关于context包

context 包的主要用途是定义 Context 类型和支持 取消!是的,您没听错;context 包的主要用途是支持取消操作,因为有时为了某种原因,您想要废弃正在做的操作。然而,能提供一些关于您取消决定的额外信息是非常有帮助的。context 包就能让您做到这点!

如果您浏览一下 context 包点源码,就会认识到它的实现是相当的简单——甚至 Context 类型的实现也非常简单,而 context 包是非常重要的。

context 包以前是作为外部 Go 包存在,但在 Go 1.7版本,它第一次作为标准 Go 包出现。因此,如果您有个较老的 Go 版本,再没先下载 context 包前,您不能跟着这节学习。

Context 类型是一个有四个方法的接口,方法名为 Deadline()Done()Err()Value()。好消息是您不需要实现 Context 接口的所有方法——您只需要使用如 context.WithCancel()context.WithTimeout() 函数修改 Context 变量就行。

下面是使用 context 包的示例,用 simpleContext.go 文件源码,分六部分来介绍。

simpleContext.go 的第一部分代码如下:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "time"
  8. )

simpleContext.go 的第二部分如下:

  1. func f1(t int) {
  2. c1 := context.Background()
  3. c1, cancel := context.WithCancel(c1)
  4. defer cancel()
  5. go func(){
  6. time.Sleep(4 * time.Second)
  7. cancel()
  8. }()

f1() 函数只需要一个时延参数,因为其他的都定义在函数里了。注意 cancel 变量的类型是 context.CancelFunc

您需要调用 context.Background() 函数来初始化一个空 Context 参数。context.WithCancel() 函数使用一个存在的 Context 创建一个子类并执行取消操作。context.WithCancel() 函数也会创建一个 Done 通道,如上面的代码所示当 cancel() 函数被调用时,或者当父 context 的 Done 通道关闭时,它会被关闭。

simpleContext.go 的第三部分包含 f1() 函数的其余部分:

  1. select {
  2. case <- c1.Done():
  3. fmt.Println("f1():", c1.Err())
  4. return
  5. case r := <-time.After(time.Duration(t) * time.Second):
  6. fmt.Println("f1():", r)
  7. }
  8. return
  9. }

这里您看到了 Context 变量的 Done() 函数的使用。当这个函数被调用时,您有一个取消操作。Context.Done() 的返回值是一个通道,否则您就不能在 select 语句中使用它了。

simpleContext.go 的第四部分如下:

  1. func f2(t int) {
  2. c2 := context.Background()
  3. c2, cancel := context.WithTimeout(c2, time.Duration(t)*time.Second)
  4. defer cancel()
  5. go func(){
  6. time.Sleep(4 * time.Second)
  7. cancel()
  8. }()
  9. select {
  10. case <-c2.Done():
  11. fmt.Println("f2():", c2.Err())
  12. return
  13. case r := <-time.After(time.Duration(t)*time.Second):
  14. fmt.Println("f2():", r)
  15. }
  16. return
  17. }

这部分展示了 context.WithTimeout() 函数的使用,它需要两个参数:Context 参数和 time.Duration 参数。当超时到达时,cancel() 函数自动调用。

simpleContext.go 的第五部分如下:

  1. func f3(t int) {
  2. c3 := context.Background()
  3. deadline := time.Now().Add(time.Duration(2*t) * time.Second)
  4. c3, cancel := context.WithDeadline(c3, deadline)
  5. defer cancel()
  6. go func() {
  7. time.Sleep(4 * time.Second)
  8. cancel()
  9. }()
  10. select {
  11. case <-c3.Done():
  12. fmt.Println("f3():", c3.Err())
  13. return
  14. case r := <-time.After(time.Duration(t) * time.Second):
  15. fmt.Println("f3():", r)
  16. }
  17. return
  18. }

上面的代码说明了 context.WithDeadline() 函数的使用,它需要两个参数:Context 变量和一个表示操作将要截止的时间。当期限到了,cancel() 函数自动调用。

simpleContext.go 的最后一段代码如下:

  1. func main() {
  2. if len(os.Args) != 2 {
  3. fmt.Println("Need a delay!")
  4. return
  5. }
  6. delay, err := strconv.Atoi(os.Args[1])
  7. if err != nil {
  8. fmt.Println(err)
  9. return
  10. }
  11. fmt.Println("Delay:", delay)
  12. f1(delay)
  13. f2(delay)
  14. f3(delay)
  15. }

执行 simpleContext.go 产生如下输出:

  1. $go run simpleContext.go 4
  2. Delay: 4
  3. f1(): 2018-02-13 23:30:00.271587 +0200 EET m=+4.003435078
  4. f2(): 2018-02-13 23:30:00.272678 +0200 EET m=+8.004706996
  5. f3(): 2018-02-13 23:30:00.273738 +0200 EET m=+12.005937567
  6. $go run simpleContext.go 10
  7. Delay: 10
  8. f1(): context canceled
  9. f2(): context canceled
  10. f3(): context canceled

输出较长的行是 time.After() 函数调用的返回值。它们代表程序正常操作。意味着如果该程序执行超时就会立刻被取消。

这与使用 context 包一样简单,因为介绍的代码没有对 Context 接口做任何重要的工作。不过,下节的 Go 代码将介绍一个更真实的例子。