为什么使用context
一个goroutine可以创建多个不同的goroutine来处理子任务,这时候就形成了一棵goroutine调用关系树,而这些goroutine之间往往需要传递截止时间、取消信号或其他与请求有关的数据,这时候就可以使用context。
要达到的目的
- 传递数据
- 上层任务取消后,所有的下层任务都会被取消;
- 中间某一层的任务取消后,只会将当前任务的下层任务取消,而不会影响上层的任务以及同级任务
Context的概要总结
一个接口、4种具体实现(或者说有4种类型)、还有6个函数
概念一:1个接口
Context定义是一个接口类型,所有的Ctx类型都需要实现这个接口的4个方法
type Context interface {
// 返回context何时会超时
Deadline() (deadline time.Time, ok bool)
// 在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回
Done() <-chan struct{}
// 方法返回context为什么被取消
Err() error
// 返回context相关的数据
Value(key interface{}) interface{}
}
概念二:4种实现(4中类型)
有四种类型的Context实现
第一种:emptyCtx
理解:emptyCtx本质上是个整形,emptyCtx对Context接口的实现,只是简单的返回nil、false等等,实际上什么也没做。所以emptyCtx用来作为context树的根节点
创建方法:Background跟TODO这两个函数内部都会创建emptyCtx
定义:
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
第二种:cancelCtx
理解:cancelCtx类型,是一种可取消的Context。
创建方法:WithCancel
定义:
type cancelCtx struct {
Context
mu sync.Mutex // 用来保护这几个字段的锁,以保证cancelCtx是线程安全的
done chan struct{} // 用于获取该context的取消通知
children map[canceler]struct{} // 用于存储以当前节点为根节点的所有可取消的context,以便在跟节点取消时,可以把他们一并取消
err error // 用于存储取消时指定的错误信息
}
第三种:timerCtx
理解:timerCtx在cancelCtx的基础上又封装了一个定时器和一个截止时间,这样既可以根据需要主动取消,也可以在到达deadline时通过timer来触发取消动作。要注意这个timer也会由cancelCtx.mu来保护,确保取消操作也是线程安全的
创建方法:WithTimeout跟WithDeadline这两个函数
定义:
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
第四种:valueCtx
理解: 用来支持键值对打包,WithValue函数可以给Context附加一个键值对信息,然后就可以通过Context传递数据了
创建方法:WithValue
定义:
type valueCtx struct {
Context
key, val interface{}
}
概念三:6个方法
创建4中context示例的6个函数
| 函数 | 用处 |
Background | func Background() Context | 用于创建emptyCtx类型的Context。特点:作为Context树的根节点 |
TODO | func TODO() Context | 用于创建emptyCtx类型的Context。特点:官方文档建议在本来应该使用外层传递的ctx,而外层却没有传递的地方使用,就像函数名称表达的含义一样,留下一个TODO |
WithCancel | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) | 用于创建cancelCtx类型的Context |
WithTimeout | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) | 用于创建timerCtx类型的Context。特点:WithTimeout函数接收一个时间段 |
WithDeadline | WithDeadline(parent Context, d time.Time) (Context, CancelFunc) | 用于创建timerCtx类型的Context。特点:WithDeadline函数需要指定一个时间点 |
WithValue | func WithValue(parent Context, key, val interface{}) Context | 用于创建valueCtx类型的Context |
示例1:
package main
import (
"context"
"fmt"
"time"
)
// parentGoroutine 父G
func parentGoroutine(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("收到信号,父ctx err=", ctx.Err())
return
default:
fmt.Println("父ctx监控中")
time.Sleep(1 * time.Second)
}
}
}
// childrenGoroutine 子G
func childrenGoroutine(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("收到信号,子ctx err=", ctx.Err())
return
default:
fmt.Println("子ctx监控中")
time.Sleep(1 * time.Second)
}
}
}
func main() {
// 创建一个根节点
ctx := context.Background()
// 可退出的Context
parentCtx, cancelFunc := context.WithCancel(ctx)
// 监控parentCtx 的 done 通知
go parentGoroutine(parentCtx)
timerChildrenCtx, _ := context.WithCancel(parentCtx) // 收到父G信号,退出
// 监控childrenCtx 的 done 通知
go childrenGoroutine(timerChildrenCtx)
cancelFunc() // 通知parentCtx,退出
time.Sleep(3 * time.Second)
}
输出结果:
收到信号,父ctx err= context canceled
收到信号,子ctx err= context canceled
示例2:
package main
import (
"context"
"fmt"
"time"
)
// parentGoroutine 父G
func parentGoroutine(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("收到信号,父ctx err=", ctx.Err())
return
default:
fmt.Println("父ctx监控中")
time.Sleep(1 * time.Second)
}
}
}
// childrenGoroutine 子G
func childrenGoroutine(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("收到信号,子ctx err=", ctx.Err())
return
default:
fmt.Println("子ctx监控中")
time.Sleep(1 * time.Second)
}
}
}
func main() {
// 创建一个根节点
ctx := context.Background()
// 可退出的Context
parentCtx, _ := context.WithTimeout(ctx, 3*time.Second) // 超时退出
// 监控parentCtx 的 done 通知
go parentGoroutine(parentCtx)
timerChildrenCtx, _ := context.WithCancel(parentCtx) // 收到父G信号,退出
// 监控childrenCtx 的 done 通知
go childrenGoroutine(timerChildrenCtx)
time.Sleep(6 * time.Second)
}
输出结果:
父ctx监控中
父ctx监控中
子ctx监控中
父ctx监控中
子ctx监控中
收到信号,子ctx err= context deadline exceeded
收到信号,父ctx err= context deadline exceeded