标准库Context
由于goroutine没有父子关系,多个goroutine都是被平行的调度,所以在拉起多个goroutine后,程序的执行模型并没有维护树状结构的goroutine树,所以无法靠语法层面,通知树中所有goroutine退出。context库就是为了解决这个问题而被引入,目的:
(1) 退出通知机制:通知可以传递给整个goroutine调用树上的每一个goroutine
(2) 传递数据:数据可以传递给goroutine调用树上的每一个goroutine
1. context—基础数据结构
——构造取消树根节点对象的两个函数
func Background() Context | 构造根节点,用作WithConcel的实参 | |
---|---|---|
func Todo() Context |
——构建不同功能的Context对象
这些方法都有一个参数parent,即在goroutine调用链中,每层都对Context实例包装自己所需的功能,并传递到下一层。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) | 带有退出通知的Context对象 |
---|---|
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) | 带有超时通知的Context对象 |
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) | 带有超时通知的Context对象 |
func WithValue(parent Context, key, value interface{}) Context | 能够传递数据的Context对象 |
2. context—用例
ctxa.children——>ctxb、ctxb.children——>ctxc
- ctxa 创建时:&cancelCtx{Context: new(emptyCtx)}
- ctxb 创建时: &timerCtx{cancelCtx: ctxa deadline:tm} 同时出发ctxa,在children中加入ctxb
- ctxc 创建时:&cancelCtx{Context: mc} 同时通过mc.Context找到ctxb,通过ctxb.cancelCtx找到ctxa,在ctxa的children中加入ctxc
type selfcontext struct {context.Context
}func work(ctx context.Context, name string) { //一个子任务,参数包含上下文信息与子任务名for {select {case <-ctx.Done(): //从上下文接收到结束信息fmt.Printf("%s get msg to cancel\n", name)returndefault:fmt.Printf("%s is running\n", name)time.Sleep(1 * time.Second)}}
}func workWithValue(ctx context.Context, name string) { //一个子任务,参数包含带值的上下文信息与子任务名for {select {case <-ctx.Done(): //从上下文接收到结束信息fmt.Printf("%s get msg to cancel\n", name)returndefault:value := ctx.Value("key").(string) //获取上下文中存储的key值fmt.Printf("%s is running and value is %s\n", name, value)time.Sleep(1 * time.Second)}}
}func ContextTest() {//使用background构建一个WithCancel类型的上下文ctxa, cancel := context.WithCancel(context.Background())go work(ctxa, "work1") //在此上下文,开启一个goroutine//使用ctxa构建一个WithDeadline类型的上下文tm := time.Now().Add(3 * time.Second)ctxb, _ := context.WithDeadline(ctxa, tm)go work(ctxb, "work2") //在此上下文,开启一个goroutine//使用ctxb构建一个WithValue类型的上下文mc := selfcontext{ctxb}ctxc := context.WithValue(mc, "key", "i am work3")go workWithValue(ctxc, "work3") //在此上下文,开启一个goroutinetime.Sleep(10 * time.Second) //经过10s,由于ctxc,ctxb都包含Deadline上下文,所以都已经超时停止//显示调用wordk1的cancal()方法,结束work1cancel()
}
总结流程如下:
- 创建一个Context根对象 (通过Background或TODO创建)
- 包装上一步的Context对象,并加入特有功能 (通过WithCancel,WithTimeout,WithDeadline,WithValue创建)
- 将上一步创建的对象作为实参传递给后续启动的并发函数(一般作为其第一个参数),每个并发函数可以继续使用包装函数对传进来的Context对象进行包装,添加自己所需要的功能。
- 顶端的goroutine在超时后调用cancel退出通知函数,通知后面的所有goroutine释放资源。
- 后面的goroutine通过select监听Context.Done()返回的chan,及时相应前端goroutine的退出通知,释放资源。