1 Go 中的 Context
Golang 的上下文也是应用开发常用的并发控制工具。同理,上下文可以用于在程序中的 API 层或进程之间共享请求范围的数据,除此之外,Go 的 Context 库还提供取消信号(Cancel)以及超时机制(Timeout)。
Context 又被称为上下文,与 WaitGroup 不同的是,Context 对于派生 goroutine 有更强的控制力,可以管理多级的 goroutine。
但我们在 Go 中创建一个 goroutine 时,如果发生了一个错误,并且这个错误永远不会终止,而其他程序会继续进行。加入有一个不被调用的 goroutine 运行无限循环,如下所示:
package main import "fmt" func main() { dataCom := []string{"alex", "kyrie", "kobe"} go func(data []string) { // 模拟大量运算的死循环 }(dataCom) // 其他代码正常执行 fmt.Println("剩下的代码执行正常逻辑") }
上面的例子并不完整,dataCom
goroutine 可能会也可能不会成功处理数据。它可能会进入无限循环或导致错误。我们的其余代码将不知道发生了什么。
有多种方法可以解决这个问题。其中之一是使用通道向我们的主线程发送一个信号,表明这个 goroutine 花费的时间太长,应该取消它。
package main import ( "fmt" "time" ) func main() { stopChannel := make(chan bool) dataCom := []string{"alex", "kyrie", "kobe"} go func(stopChannel chan bool) { go func(data []string) { // 大量的计算 }(dataCom) for range time.After(2 * time.Second) { fmt.Println("此操作运行时间过长,取消中") stopChannel <- true } }(stopChannel) <-stopChannel // 其他代码正常执行 fmt.Println("剩下的代码执行正常逻辑") }
上面的逻辑很简单。我们正在使用一个通道向我们的主线程发出这个 goroutine 花费的时间太长的信号。但是同样的事情可以用 context 来完成,这正是 context 包存在的原因。
package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) defer cancel() dataCom := []string{"alex", "kyrie", "kobe"} go func() { go func(data []string) { // 大量的计算 }(dataCom) for range time.After(2 * time.Second) { fmt.Println("此操作运行时间过长,取消中") cancel() return } }() select { case <-ctx.Done(): fmt.Println("上下文被取消") } }
2 Context 接口
Context 接口定义:
type Context interface { Deadline() (deadline time.Time, ok bool) Done <-chan struct{} Err() error Value(key interface{}) interface{} }
Context 接口定义了 4 个方法:
Deadline()
: 返回取消此上下文的时间 deadline(如果有)。如果未设置 deadline 时,则返回 ok==false,此时 deadline 为一个初始值的 time.Time 值。后续每次调用这个对象的 Deadline 方法时,都会返回和第一次调用相同的结果。Done()
: 返回一个用于探测 Context 是否取消的 channel,当 Context 取消会自动将该 channel 关闭,如果该 Context 不能被永久取消,该函数返回 nil。例如context.Background()
;如果Done
被 close,Err 方法会返回 Done 被 close 的原因。Err()
: 该方法会返回 context 被关闭的原因,关闭原因由 context 实现控制,不需要用户设置;如果Done()
尚未关闭,则Err()
返回 nilValue()
: 在树状分布的goroutine
之间共享数据,用 map 键值的工作方法,通过 key 值查询 value。
每次创建新上下文时,都会得到一个符合此接口的类型。上下文的真正实现隐藏在这个包和这个接口后面。这些是您可以创建的工厂类型的上下文:
context.TODO
context.Background
context.WithCancel
context.WithValue
context.WithTimeout
context.WithDeadline