Golang 语言中 Context 的使用方式
一、介绍
在Golang 语言并发编程中,经常会遇到监控 goroutine 运行结束的场景,通常我们会想使用WaitGroup 和 chan + select , 其中WaitGroup 用于监控一组goroutine 是否全部运行结束,chan + select 用于监控一个 goroutine 是否运行结束(取消一个 goroutine )
如果我们需要监控多个goroutine 是否运行结束,或者 取消多个。通常会使用context ,当然使用context 也可以用于监控一个 goroutine 是否运行结束。
二、取消一个 goroutine
使用context 取消一个 goroutine ,比较类似实用 chan + select 的方式取消一个 goroutine
示例代码:
package mainimport ("context""fmt""time"
)func main() {ctx, cancel := context.WithCancel(context.Background())go func(ctx context.Context) {for {select {case <-ctx.Done():fmt.Println("goroutine 已停止")returndefault:fmt.Println("goroutine 正在运行")time.Sleep(time.Second)}}}(ctx)time.Sleep(time.Second * 5)cancel()time.Sleep(time.Second * 5)fmt.Println("goroutine 已结束")
}
运行结果:
阅读上面这段代码,我们首先使用 context.Background() 创建一个context 树的根节点,然后使用context.WithCancel() 创建一个可取消的走context 类型的变量,ctx ,作为参数传递给子goroutine ,用作跟踪子goroutine 。
然后在子 goroutine 中使用for select 监控 <- ctx.Done() 判断goroutine 是否运行结束。
最后使用 context.Withcancel() 返回的第二个值CancelFunc 类型的cancel 变量给子goroutine 发送取消指令
三、取消多个goroutine
下面,我们再来看看一个使用context 停止多个goroutine 的示例
package mainimport ("context""fmt""time"
)func main() {ctx, cancel := context.WithCancel(context.Background())go worker(ctx, "节点一")go worker(ctx, "节点二")go worker(ctx, "节点三")go worker(ctx, "节点四")time.Sleep(time.Second * 5)cancel()time.Sleep(time.Second * 5)fmt.Println("main goroutine 已结束")
}func worker(ctx context.Context, node string) {for {select {case <-ctx.Done():fmt.Println(node, "goroutine 已停止")returndefault:fmt.Println(node, "goroutine 正在运行")time.Sleep(time.Second)}}
}
运行结果:
阅读上面这段代码,我们使用go 关键字启动三个 worker goroutine ,和上个示例一样,首先创建一个context 树的节点,使用第一个返回值context 类型的字ctx 跟踪每一个worker goroutine ,在worker 中使用for select 监控 <- ctx.Done() 判断子 goroutine 是否运行结束,通过调用第二份返回值CancelFunc 类型cancel 给子goroutine 发送取消指令,此时所有字context 都会接受到取消指令,goroutine 结束运行
四、上下文信息传递
我们在前面的示例中使用WithCancel 函数,用作取消context,除此之外,可用作取消Context 的函数还有WithDeadline 函数和WithTimeout 函数,分别用于定时取消和超时取消;在Golang中 context 包还有一个重要的作用,就是作用上下文传递,请看示例代码:
package mainimport ("context""fmt""time"
)func main() {ctx, cancel := context.WithCancel(context.Background())// 传递上下文信息ctxValue := context.WithValue(ctx, "uid", 1)go func(ctx context.Context) {for {select {case <-ctx.Done():fmt.Println(ctx.Value("uid"), "goroutine 已停止")returndefault:fmt.Println("goroutine 正在运行")time.Sleep(time.Second)}}}(ctxValue)time.Sleep(time.Second * 5)cancel()time.Sleep(time.Second * 5)fmt.Println("main goroutine 已结束")}
运行结果:
阅读上面的代码,我们使用了 WithValue 函数给子级 goroutine 传递上下文信息uid.。 WithValue 函数接收三个参数,分别是parent context ,key 和value 。返回值是一个context ,我们可以在子级 goroutine 中调用Value 方法获取传递的上下文信息。