关于go中的通道可以这样理解:
- 通道类似一个
没有持久化功能
的, 完全基于内存
的, 消息队列 - 若创建通道时
未设置通道容量
,则协程在向通道中存放数据后会一直等待
,直到其它协程取走数据后才会向下执行; - 若创建通道时
设置通道容量
,则协程在向通道中存放数据后会继续向下执行,不会阻塞
; - 在向通道中存放数据时,若通道
已满
,则当前协程会一直等待
,直到通道空闲; - 在从通道中读取数据时,若通道中
不存在数据
,则当前协程会一直等待
,直到其它协程向通道中存放数据。
下面举个例子
func TestDoSomething05(t *testing.T) {
res := asyncService()
task02()
time.Sleep(time.Second * 5)
fmt.Println("接收到", <-res)
time.Sleep(time.Second * 1)
}
func task02() {
fmt.Println("task 02 done")
}
func task01() string {
return "task 01 done"
}
func asyncService() chan string {
retCh := make(chan string)
go func() {
ret := task01()
fmt.Println("start....")
retCh <- ret
fmt.Println("end....")
}()
return retCh
}
# 执行结果会先打印出
task 02 done
start....
注意: 此时5秒钟过去
接着打印
接收到 task 01 done
end....
这时会执行完毕
明显会看到 fmt.Println("end....")
这行代码被阻塞了5秒
阻塞的原因就是:
- 上面我说过的, 重复一遍, 若创建通道时
未设置通道容量
,则协程在向通道中存放数据后会一直等待
,直到其它协程取走数据后才会向下执行 - 示例1没有设置通道容量, 同时通道接收端有5秒的sleep, 造成了这个情况
下面我进行如下修改
- 我设置通道容量, 使通道变为缓存通道
也就是这样:
retCh := make(chan string, 2)
代码就变成了如下结构
func TestDoSomething05(t *testing.T) {
res := asyncService()
task02()
time.Sleep(time.Second * 5)
fmt.Println("接收到", <-res)
time.Sleep(time.Second * 1)
}
func task02() {
fmt.Println("task 02 done")
}
func task01() string {
return "task 01 done"
}
func asyncService() chan string {
retCh := make(chan string, 2)
go func() {
ret := task01()
fmt.Println("start....")
retCh <- ret
fmt.Println("end....")
}()
return retCh
}
# 执行结果会先打印出
task 02 done
start....
end....
注意: 此时5秒钟过去
接着打印
接收到 task 01 done
明显会看到 fmt.Println("end....")
这行代码没有被阻塞
仅仅只有主线程的fmt.Println("接收到", <-res)
也就是只有通道的另一端接收者
被sleep 等待了5秒
还有一句想说的:
可能也是最重要的
我看好多人都被日志前后顺序迷惑了,
不要被fmt 或者 t.log 迷惑 chan 接到消息 到 打印日志 这还有一段路要走
哪边走的快这个不一定的 两回事 要谨慎使用sleep时间来写demo 位置/时间差一定要合适