context 可以说是Golang
中非常重要的包了,于是乎学习了一番,总结以作备忘。
文章包含以下内容:
- context 是什么
- context 的作用及使用姿势
- context 使用时要注意什么
context 是什么
Go 1.7 标准库引入
context
,中文译作“上下文”,确切的说它是goroutine
的上下文,包含了goroutine
的运行状态、环境、现场等信息。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
该接口定义了四个需要实现的方法:
Deadline
:获取当前 context 的取消时间;Done
:返回一个只读的 channel,类型为结构体。这个 Channel 会在当前工作完成或者上下文被取消后关闭,多次调用 Done 方法会返回同一个 channel;Err
:获取当前 context 被取消的原因;Value
:获取当前 context 对应所存储的上下文信息。
context 的作用及使用姿势
context
主要作用在多个goroutine
组成的树中同步取消信号以减少对资源的消耗和占用,同时它也有传值的功能。
取消 goroutine
协程中使用 select
,来监听‘父协程’(这个的父协程是虚指代,也可能是父父协程)是否结束、取消。
代码如下:
func main() {
ctx, cancel := context.WithCancel(context.Background())
go goroutine1(ctx)
go goroutine2(ctx)
time.Sleep(time.Second * 3)
cancel()
time.Sleep(time.Second * 3)
}
func goroutine1(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case <-time.After(time.Second):
fmt.Println("this is one")
}
}
}
func goroutine2(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case <-time.After(time.Second):
fmt.Println("this is two")
}
}
}
输出结果:
Written@liqilin01 go_code$ go run test004.go
this is two
this is one
this is one
this is two
传递数据
传递数据也是一个很重要的功能,比如在一次请求的生命周期中传递
用户的信息
。
代码如下:
// 写入
userInfo := map[string]interface{} {
"user_id": int64(1),
"display_name": "大脑斧",
"avatar": "---",
"phone": "---",
}
newCtx := context.WithValue(ctx, "user_info", userInfo)
// 使用
userInfo := ctx.Value("user_info").(map[string]interface{})
注意事项
在官方文档中,对于使用 context 提出了几点建议:
- Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx.
- Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.
- Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
- The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.
- 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
- 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
- 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
- 同一个 context 可能会被传递到多个 goroutine,不过没关系,context 是并发安全的。
思考
- context 是并发安全的 -> 因为使用了锁
// 以移除 child 为例
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
参考
- https://qcrao91.gitbook.io/go/biao-zhun-ku/context
- https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/
- https://mp.weixin.qq.com/s/5JDSqNIimNrgm5__Z4FNjw
- https://mp.weixin.qq.com/s/A03G3_kCvVFN3TxB-92GVw