文章链接:https://mp.weixin.qq.com/s/FJLH4o7Y1TG9I0seiNwR_w
https://maiyang.me/post/2018-02-12-how-to-correctly-use-context.context-in-golang/
context是一个很好的解决多goroutine下通知传递和元数据的Go标准库。由于Go中的goroutine之间没有父子关系,因此也不存在子进程退出后的通知机制。多个goroutine协调工作涉及 通信,同步,通知,退出 四个方面:
通信:chan通道是各goroutine之间通信的基础。注意这里的通信主要指程序的数据通道。
同步:可以使用不带缓冲的chan;sync.WaitGroup为多个gorouting提供同步等待机制;mutex锁与读写锁机制。
通知:通知与上文通信的区别是,通知的作用为管理,控制流数据。一般的解决方法是在输入端绑定两个chan,通过select收敛处理。这个方案可以解决简单的问题,但不是一个通用的解决方案。
退出:简单的解决方案与通知类似,即增加一个单独的通道,借助chan和select的广播机制(close chan to broadcast)实现退出。
但由于Go之间的goroutine都是平等的,因此当遇到复杂的并发结构时处理退出机制则会显得力不从心。因此Go1.7版本开始提供了context标准库来解决这个问题。他提供两个功能:退出通知和元数据传递。他们都可以传递给整个goroutine调用树的每一个goroutine。同时这也是一个不太复杂的,适合初学Gopher学习的一段源码。
工作机制
第一个创建Context的goroutine被称为root节点:root节点负责创建一个实现Context接口的具体对象,并将该对象作为参数传递至新拉起的goroutine作为其上下文。下游goroutine继续封装该对象并以此类推向下传递。
package main
import (
"context"
"time"
)
func main() {
// withcancel func
ctxCal, canlce := context.WithCancel(context.Background())
// withtimeout func
ctxTim, _ := context.WithTimeout(ctxCal, time.Second*3)
go work(ctxTim, "work1")
//time.Sleep(10*time.Second)
// withvalue func
ctxVal := context.WithValue(ctxTim, "key", "custom value")
go workWithValue(ctxVal, "work3")
time.Sleep(1*time.Second)
canlce()
time.Sleep(2*time.Second)
}
func workWithValue(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
println(name, "get message to quit")
return
default:
value := ctx.Value("key").(string)
println(name, "is running with value", value)
time.Sleep(time.Second)
}
}
}
func work(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
println(name, "get message to quit")
return
default:
println(name, "is running")
time.Sleep(time.Second)
}
}
}
work3 is running with value custom value
work1 is running
work1 is running
work3 get message to quit
work1 get message to quit
可以看到,当ctxb因超时而退出之后,会通知由他包装的所有子goroutine(ctxc),并通知退出。各context的关系结构如下: