context包构建了树型关系的Context。go Context底层实际上是通过使用 channel + mutex 来实现的。channel负责在父级节点cancel()
后的相关子协程之间广播通信,而mutex则保证了ctx在多个 goroutine 之间传递时的线程安全。
使用context时,首先要创建一个顶级的context,也就是context.Background()
每次用户请求到来时,向一组具有上下文关系的 goroutine 中分别传入ctx 参数,并分别监听ctx.Done()方法。
Done()
方法返回一个只读的channel,所有相关函数监听此channel。一旦channel关闭,所有负责监听的 goroutine 通过Go channel被关闭时的广播机制,都能够收到通知。
子goroutine可以通过 select-case 的方式检查自身是否被父级节点cancel()
,一旦上层环境(父节点)撤销了本 goroutine 的执行,应当终止对当前请求信息的处理,释放资源并return。
正因为上述方式,一个request范围内所有 goroutine 运行时的取消能得到有效控制。
Background()
WithCancel()
返回一个子context对象和一个cancel()函数,可以主动停止goroutine。WithDeadline()
设置一个时间点,到点后执行cancel()方法。WithTimeout()
设置一个time.Duration,到时间则会cancel这个context。WithValue()
可以设置一个 k/v 的键值对,可在下游任何一个嵌套的context中通过key获取value,但是不建议使用这种来做goroutine之间的通信。这个值一般是线程安全的.Done()
方法,子goroutine可以通过 select-case 的方式检查自身是否被父级节点Cancel,一旦上层环境(父)撤销了本goroutine的执行,应当终止对当前请求信息的处理,释放资源并return:// (1) 顶层Context:Background():返回一个空的Context,它作为所有由此继承Context的根节点
func Background() Context {
}
// (2) context库中,有4个关键方法
// 带cancel返回值的Context,一旦cancel被调用,即取消该创建的context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
}
// 带有效期cancel返回值的Context,即必须到达指定时间点调用的cancel方法才会被执行
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
}
// 带超时时间cancel返回值的Context,类似Deadline,前者是时间点,后者为时间间隔
// 相当于WithDeadline(parent, time.Now().Add(timeout)).
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
}
// (3) Context提供的 Done()方法
select {
case <-ctx.Done():
// do some clean…
// 主动终止对当前请求信息的处理,释放资源并返回
}
注意:父节点Context可以主动通过调用cancel方法取消子节点Context,而 子节点Context只能被动等待。同时父节点Context自身一旦被取消(如其上级节点Cancel),其下的所有子节点Context均会自动被取消。
package main
import (
"context"
"fmt"
"time"
)
func someHandler() {
// 创建继承Background的子节点Context
ctx, cancel := context.WithCancel(context.Background())
// ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second) // 3秒后提前结束doSth()
go doSth(ctx)
//模拟程序运行 - Sleep 5秒
time.Sleep(5 * time.Second) // 避免main()函数会提前结束
cancel()
}
//每1秒work一下,同时会判断ctx是否被取消,如果是就退出
func doSth(ctx context.Context) {
var i = 1
for {
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println("done")
return
default:
fmt.Printf("work %d seconds. \n", i)
}
i++
}
}
func main() {
fmt.Println("start...")
someHandler()
fmt.Println("end.")
}
使用原则:
Context使用原则:
context.TODO()
缺点:
一旦代码中某处用到了Context,传递Context变量(通常作为函数的第一个参数)会像病毒一样蔓延在各处调用它的地方。即每一个相关函数都必须增加一个context.Context类型的参数,且作为第一个参数,这对无关代码完全是侵入式的。