背景
Context 已经成为 golang 并发编程中 如何控制超时的标准解决方案了。网上很多 context 的用法例子,但大多数都不够精短。为了能用最少的代码说清楚 context 的精髓用法,特意写了这个帖子。
场景1 主动取消go routine 的执行有些场景中,我们需要手动地结束 go rouinte 运行。比如某个 go routine 一直在等待某个资源,但这个资源永远都不会就绪,这时候需要提前结束这个 go routine。
package main
import (
"context"
"fmt"
"time"
)
func watch(ctx context.Context, contexName string) {
go func() {
for {
select {
case <-ctx.Done():
fmt.Println(contexName, " stop ")
return
default:
time.Sleep(100*time.Millisecond)// 每100ms监测一次
}
}
}()
}
func main() {
rootCtx:=context.Background()
ctx1,_ :=context.WithDeadline(rootCtx, time.Now().Add(time.Second))// 1秒后结束运行,到达某个时间点结束
watch(ctx1,"WithDealine context")
ctx2,_:=context.WithTimeout(rootCtx, 2*time.Second)// 只运行2秒, 从开始运行算起
watch(ctx2,"WithTimeout context")
ctx3,cancel:= context.WithCancel(rootCtx)// 可以手动结束的 context
watch(ctx3,"WithCancel context")
time.Sleep(3*time.Second)
cancel()//3秒后手动结束运行
time.Sleep(time.Second)
}
场景2 链路传值
package main
import (
"context"
"fmt"
)
func A(ctx context.Context) {
fmt.Println("This is function A")
ctxB:=context.WithValue(ctx,"k1","v1")
B(ctxB)
}
func B(ctx context.Context) {
fmt.Println("This is function B")
ctxC:=context.WithValue(ctx,"k2","v2")
C(ctxC)
}
func C(ctx context.Context) {
fmt.Println("This is function C")
fmt.Println(ctx.Value("k1"))
fmt.Println(ctx.Value("k2"))
}
func main() {
rootCtx:=context.Background()
A(rootCtx)
}
总结一下
- Context 一共有6种 Background, Todo, WithDeadline, WithTimeout, WithCanel, WithValue。前两种本质上都是 一个空的 empty 用作 context 的 "种子", 中间三种 用于 终止代码的运行, 最后一种用于传值
- Context的核心用法是主动终止代码的运行,次要用法才是链路传值。
- Done() 方法会返回一个 可读的channel, 若通过 select case :<- contex.Done() 读到了说明这个 context应该结束了,通常要 break 或者 return。 特别的是, 这个 channel 是不带 buffer的, select语句记得写 default, 否则死锁报错。
参考资料
juejin.cn/post/684490… juejin.cn/post/709610…