您现在的位置是:首页 >技术教程 >go 协程.通道.select网站首页技术教程
go 协程.通道.select
go中的方法
方法的申明:
func (t Type) methodName(parameter list) return List{ 逻辑执行 }
go 中相同的方法名 可以定义在不同的类型(结构)上,相同名字的函数是 不被容许的
go 中修改结构体字段 需要使用指针接收器或指针(值类型或者值接收器 不可以); 指针接收器内部属性的改变对于 调用者是可见的, 值接收器内部属性的改变 对于调用者不可见;所有当想改变实例属性的时候 必须使用指针
t Type 接收器(可以是结构体或非机构体类型,接收器可在方法内部访问) 可以缺省
parameter list 参数列表 可缺省
return List 返回值列表 可缺省
type Person struct { name string icon string age uint8 } type Author struct { name string } func (p Person) PrintInfo() { fmt.Println("Person name:", p.name) fmt.Println("Person icon:", p.icon) fmt.Println("Person age:", p.age) } func (p Person) ChangePersonName(name string) { p.name = name fmt.Println("通过结构体修改 Person 新名字:", p.name) } func (p *Person) ChangePersonNameByPoint(name string) { p.name = name fmt.Println("通过指针修改 Person 新名字:", p.name) } func (au Author) PrintInfo() { fmt.Println("Author name:", au.name) } func PrintInfo(p Person) { fmt.Println("name:", p.name) fmt.Println("icon:", p.icon) fmt.Println("age:", p.age) } 输出: Person name: 王二狗 Person icon: https://aaa.png Person age: 23 name: 王二狗 icon: https://aaa.png age: 23 Author name: 王大锤 --------------------------- 通过结构体修改 Person 新名字: 我是男一号 Person name: 王二狗 Person icon: https://aaa.png Person age: 23 --------------------------- 通过指针修改 Person 新名字: 我是男二号 Person name: 我是男二号 Person icon: https://aaa.png Person age: 23 x.add(y) 154
接口
协程
协程 是与其他函数或方法 一起并发运行的工作方式, 协程可以看做是 轻量级线程;所以创建一个协程的成本更小
协程不是原子性的
开启协程关键字 go
func FuncPrintInfo() { fmt.Println("我是一个函数我的名称叫 PrintInfo") } // 多个协程 func PrintNum(num int) { for i := 0; i < 3; i++ { fmt.Println("PrintNum:", num) //time.Sleep(100 * time.Millisecond) } } func CoroutineMethod() { go FuncPrintInfo() //如果不加 time.Sleep(1 * time.Second) 则 主程序运行完成后还未等协程运行完毕 程序就已经停止了 time.Sleep(1 * time.Second) fmt.Println("CoroutineMethod") time.Sleep(1 * time.Second) 开启1号协程 go PrintNum(1) 开启2号协程 go PrintNum(2) time.Sleep(time.Second) } 输出: 我是一个函数我的名称叫 PrintInfo CoroutineMethod PrintNum: 1 PrintNum: 1 PrintNum: 1 PrintNum: 2 PrintNum: 2 PrintNum: 2
通道
通道 channel 是一个管道,可以想现象成 Go协程之间通讯的管道。 他是一种队列式的 数据结构,遵循 先进先出的规则;通道使用完毕需要关闭:
通道主要分为两种:
- 缓冲通道
c := make(chan int,5)
- 无缓冲通道:在无缓冲通道中无法存储数据, 所有接收端先于 发送端开始操作,当发送端 发送数据,接收端立马取走数据。否则 发送端就会造成阻塞
c := make(chan int)
创建通道的时候 可以设置第二个参数 容量,当容量为0的时候 通道里面不能存放数据,发送数据的时候必须有人 立马去接收负责就会报错。此时的通道叫做 无缓冲通道,
如果创建通道的时候 设置 容量为1,此时通道里面已经有一个数据,此时再往里面发送数据 就会报错 造成阻塞。**可以利用此特性 来做锁**通道特性:
cap 获取通道容量 len 获取通道长度 //指定通道参数长度 chInt := make(chan int, 3) fmt.Printf("chInt 初始化之后的容量长度:%d, 内容长度%d ", cap(chInt), len(chInt)) chInt <- 0 chInt <- 1 chInt <- 2 fmt.Printf("chInt 传入参数之后的容量长度:%d, 内容长度%d ", cap(chInt), len(chInt)) <-chInt fmt.Printf("chInt 取出值之后的容量长度:%d, 内容长度%d ", cap(chInt), len(chInt)) 输入: chInt 初始化之后的容量长度:3, 内容长度0 chInt 传入参数之后的容量长度:3, 内容长度3 chInt 取出值之后的容量长度:3, 内容长度2
- 只发送通道
只接收通道
双向通道
//只发 送通道 type Sender = chan<- string //只接收通道 type Receiver = <-chan string //双向通道 var chDouble = make(chan string)
通道的遍历:
var ch2 = make(chan int, 5) go func() { for i := 0; i < 10; i++ { ch2 <- i } }() go func() { for v := range ch2 { fmt.Println(v) } }()
在无缓冲通道 中无法存储数据, 所有接收端先于 发送端开始操作,当发送端 发送数据,接收端立马取走数据。否则 发送端就会造成异常阻塞,出现异常2 fatal error: all goroutines are asleep - deadlock!
// 出现异常1 fatal error: all goroutines are asleep - deadlock! //ch0 := make(chan int) //ch0 <- 1
// 出现异常2 fatal error: all goroutines are asleep - deadlock! //ch1 := make(chan int) //ch1 <- 1 //println("取数据", <-ch1)
上面的问题 可以使用协程来解决:
ch := make(chan int) go func() { println("取数据", <-ch) }() ch <- 1 //延迟 用于打印内容 time.Sleep(time.Second) 输出: 取数据 1
协程不是 原子操作.应该避免对个协程对同一个数据进行操作,使用通道容量为 1的通道,达到锁的效果
// 通道 ch 容量为1 // ch 传入一个,再去出一个 形成一个锁 func increment(ch chan bool, x *int) { ch <- true *x = *x + 1 //不是原子操作,应该避免对个协程对同一个数据进行操作,使用通道容量为 1的通道,达到锁的效果 <-ch } ch3 := make(chan bool, 1) var x int for i := 0; i < 10000; i++ { go increment(ch3, &x) } time.Sleep(time.Second) fmt.Println("x = ", x) 输入:x = 10000
不使用锁的情况下:
func increment(x *int) { *x = *x + 1 } var x int for i := 0; i < 10000; i++ { go increment(&x) } time.Sleep(time.Second) fmt.Println("x = ", x) 输出:x = 9840
协程中的异步转同步
开发过程中我们经常要等待 一个协程的完成;才进行下一步骤:
- 使用通道等待协程完成
isDone := make(chan bool) go func() { for i := 0; i < 5; i++ { fmt.Println(i) //模拟 耗时操作 time.Sleep(time.Second) } isDone <- true }() <-isDone fmt.Println("等待所有完成") 输出: 0 1 2 3 4 等待所有完成
- 使用 WaitGroup 等待一组协程完成
/* WaitGroup 等待一组(协程)任务结束,在执行其他业务 Add() 初始值为0 累加协程数量 Done() 当某个子协程完成后 计数器减去1, 通常用 defer 调用 Wait() 阻塞当前协程, 直到实例中的计数器归零 */ func DoTask(taskNum int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 3; i++ { fmt.Printf("task:%d : %d ", taskNum, i) time.Sleep(180 * time.Millisecond) } } var wg sync.WaitGroup wg.Add(3) //开启协程1 go DoTask(1, &wg) //开启协程2 go DoTask(2, &wg) //开启协程3 go DoTask(3, &wg) fmt.Println("等待 所有协程完成") wg.Wait() fmt.Println("完成") 输出: 等待 所有协程完成 task:2 : 0 task:3 : 0 task:1 : 0 task:3 : 1 task:2 : 1 task:1 : 1 task:2 : 2 task:3 : 2 task:1 : 2 完成
select:
- select (针对通道中进行选择) 语句用在多个 发送/接收通道操作中进行选择
- 使用 select 语句会一直阻塞 直到发送者或者接受者准备就绪
- 如果有多个通道同时准备完毕 select 会随机选取其中一个执行,否则会先返回 第一个,忽略第一个响应
- 如果有耗时操作,并且select 有default 则 default 会直接执行 忽略其他 通道
▶ select 中有 同时返回的:select 会随 机选 取其中一个执行/* select{ case exp1: code case exp2: code case exp3: code default: code } */
ch1 := make(chan string, 1) ch2 := make(chan string, 1) ch3 := make(chan string, 1) ch1 <- "我是王大锤" ch2 <- "我是王二锤" ch3 <- "我是王三锤" select { case message1 := <-ch1: fmt.Println("message1:", message1) case message2 := <-ch2: fmt.Println("message2:", message2) case message3 := <-ch3: fmt.Println("message3:", message3) default: fmt.Println("default: 什么也没收到") } 随机 输出: message2: 我是王二锤
▶ select 如果有耗时操作,且没有default 则 default; 则会 执行第一个通道 返回。否则会执行 default
func task1(ch chan string) { time.Sleep(3 * time.Second) ch <- "向通道发送数据1" } func task2(ch chan string) { time.Sleep(2 * time.Second) ch <- "向通道发送数据2" } func task3(ch chan string) { time.Sleep(4 * time.Second) ch <- "向通道发送数据3" } ch1 := make(chan string) ch2 := make(chan string) ch3 := make(chan string) go task1(ch1) go task2(ch2) go task3(ch3) select { case message1 := <-ch1: fmt.Println("message1:", message1) case message2 := <-ch2: fmt.Println("message2:", message2) case message3 := <-ch3: fmt.Println("message3:", message3) //default: // fmt.Println("default 执行了 没有等待其他通道") } 输出: message2: 向通道发送数据2
▶ select 死锁的情况
- 通道没有使用存入值得时候,直接取值 会造成 报错死锁
// 通道没有使用存入值得时候,直接取值 会造成 报错死锁 // fatal error: all goroutines are asleep - deadlock! func selectLock() { ch1 := make(chan string, 1) ch2 := make(chan string, 1) ch3 := make(chan string, 1) select { case message1 := <-ch1: fmt.Println("message1:", message1) case message2 := <-ch2: fmt.Println("message2:", message2) case message3 := <-ch3: fmt.Println("message3:", message3) } }
- 空 select
func emptySelect() { //select-case 和 switch-case 很相似,区别 switch数顺序的 select 不是顺序的 //发生死锁 fatal error: all goroutines are asleep - deadlock! select {} }