概述

context 包定义了 Context 类型,它在 API 边界和进程之间传递截止时间,取消信号,和其他请求生命周期中的值。

请求到达服务器后需创建 Context 实例,服务器的响应也应该接受 Context。在它们之间的函数调用链必须传递这个 Context 实例,也可以调用 WithCancel, WithDeadline, WithTimeout, 或 WithValue 来衍生出新 Context 实例。当取消一个 Context 实例时,所有由它衍生出的 Context 实例都会取消。

函数 WithCancel,WithDeadline,WithTimeout 接受一个 Context 实例(父级)并返回一个衍生 Context(子级) 和一个 CancelFunc。调用 CancelFunc 会取消它(子级 Context)和由它衍生的 Context 实例,删除父级与它的联系并停止所有关联的定时器。不调用 CancelFunc 会导致它和它的衍生体的生命周期和父级一样长。go vet 工具可以检查是否所有的流程控制语句都使用了 CancelFunc。

为了能让使用 Context 的程序保持包之间的接口一致性并且能够使用静态工具进行检查,我们需要遵循以下规则:

在函数中按需传递 Context 而不要将 Context 储存在结构体中。Context 应该作为函数的第一个参数,一般叫 ctx:

即使函数允许也不要传递值为 nil 的 Context。如果你不知道使用哪个 Context 可以使用 context.TODO 。

只在 API 或者进程的生命周期中的数据使用 context 的值,不将其作为函数的可选参数 。

相同的 Context 可以传递进不同的 goroutines。Context 可以安全的被多个 goroutine 同时使用。

更多详情与示例见 https://blog.golang.org/context

Package files

Variables

  1. var Canceled = errors.("context canceled")

Context.Err 在 context 取消后返回 Canceled。

  1. var DeadlineExceeded error = deadlineExceededError{}

Context.Err 在 context 超过截止时间后返回 DeadlineExceeded。

  1. type CancelFunc func()

type

  1. type Context interface {
  2. // 当请求处理完成时 Deadline 返回一个时间,这时我们需要手动取消 context。
  3. // 如果没有设置 deadline 那么 ok 为 false。
  4. // Deadline 的多次成功调用返回相同结果。
  5. Deadline() (deadline .Time, ok )
  6.  
  7. // Done 返回一个 channel,当 channel 关闭表示请求完成,需要手取消 context。
  8. // 如果 context 不能取消,那么 Done 将会返回 nil。
  9. // Done 的多次调用返回相同值。
  10. //
  11. // WithCancel 使 Done 返回的 channel 在 context 取消的时候关闭。
  12. // WithDeadline 使 Done 返回的 channel 在 context 到达截止时间的时候关闭。
  13. // WithTimeout 使 Done 返回的 channel 在超时的时候关闭。
  14. //
  15. // Done 和 select 搭配使用:
  16. //
  17. // // Stream 调用 DoSomething 初始化一个值并输出。
  18. // // 当 Done 返回的 channel 被关闭或者 DoSomething 函数返回错误 Stream 函数才会返回。
  19. // func Stream(ctx context.Context, out chan<- Value) error {
  20. // for {
  21. // v, err := DoSomething(ctx)
  22. // if err != nil {
  23. // return err
  24. // }
  25. // select {
  26. // case <-ctx.Done():
  27. // return ctx.Err()
  28. // case out <- v:
  29. // }
  30. // }
  31. // }
  32. // 在 https://blog.golang.org/pipelines 有更多用法示例。
  33. // Done channel 用来获取取消状态。
  34. Done() <-chan struct{}
  35.  
  36. // 如果 Done 没有关闭,Err 返回 nil。
  37. // 如果 Done 已经关闭,Err 回返回非 nil 值来告知 Done 因为什么关闭:
  38. // 如果 context 取消返回 Canceled。
  39. // 如果 context 到达截止时间返回 DeadlineExceeded。
  40. // 在 Err 返回非 nil 值后,每次调用 Err 都返回相同结果。
  41. Err() error
  42.  
  43. // Value 返回 key 对应的值如果没有对应的值返回 nil。对相同键的多次成功调用返回相同的值。
  44. //
  45. // 只在 API 或者进程的生命周期中的数据使用 context 的值,不将其作为函数的可选参数 。
  46. //
  47. // 在 Context 中使用 key 的区分具体值。
  48. // 希望在 context 保存值的函数一般都会在全局分配一个键,然后使用这个键作为 context.WithValue 和 Context.Value 的参数。
  49. // key 可以是任何支持相等比较的类型,使用 key 的包应该定义非导出类型避免与其他 key 冲突。
  50. //
  51. // 定义 Context 的 key 的包需要为相应的值提供类型安全的访问器:
  52. //
  53. // // user 包定义了一个保存在 Context 中的 User 类型。
  54. // package user
  55. //
  56. // import "context"
  57. //
  58. // // User 是保存在 Context 中的类型。
  59. // type User struct {...}
  60. //
  61. // // key 是包中的一个非导出类型。
  62. // // 它可以避免与其他包中定义的 key 类型产生冲突。
  63. // type key int
  64. //
  65. // // userKey 是 Context 中保存的 user.User 所对应的 key。
  66. // // 它是非导出的类型; 用户可以使用 user.NewContext 和 user.FromContext 从而避免直接使用 key。
  67. // var userKey key
  68. //
  69. // // NewContext 返回一个保存 u 的 Context。
  70. // func NewContext(ctx context.Context, u *User) context.Context {
  71. // return context.WithValue(ctx, userKey, u)
  72. // }
  73. //
  74. // // 如果存在, FromContext 返回 ctx 中保存的 User 类型值。
  75. // func FromContext(ctx context.Context) (*User, bool) {
  76. // u, ok := ctx.Value(userKey).(*User)
  77. // return u, ok
  78. // }
  79. Value(key interface{}) interface{}
  80. }

Context 穿过 API 边界传递截止时间,取消信号和其他值。

Context 的方法都能在多个 goroutines 中同时使用。

Background 返回一个非 nil 的空 Context,它不能取消,不包含值,没有截止时间。它的典型应该用场景包括:main 函数,初始化,测试,或者作为所有请求的顶级 Context。

func

  1. func TODO()

TODO 返回一个非 nil 的空 Context。当不知道该使用哪个 Context 或者没有可用的 Context 的时候(因为函数不接受 Context 参数)的时候可以使用它, TODO 可以被静态的分析工具(检测 Context 在程序中正确传递)识别。

  1. func WithCancel(parent Context) (ctx , cancel CancelFunc)

WithCancel 返回拥有 Done channel 的 parent 副本。不管调用 CancelFunc 和关闭 parent 的 Done channel 哪个先发生,Done channel 都会关闭。

取消 context 会释放和他关联的资源,所以我们应该在 Context 完成后立即取消它。


例:

  1. // gen 的调用者一旦取消 context 就不会再消费生成的数字也不会导致 gen 中的 goroutine 泄漏。
  2. gen := func(ctx context.Context) <-chan int {
  3. dst := make(chan int)
  4. n := 1
  5. go func() {
  6. for {
  7. select {
  8. case <-ctx.Done():
  9. return // 在 ctx 取消时返回,这样不会导致 goroutine 泄漏。
  10. case dst <- n:
  11. n++
  12. }
  13. }
  14. }()
  15. return dst
  16. ctx, cancel := context.WithCancel(context.Background())
  17. defer cancel() // 在我们消费完数字取消 ctx。
  18. for n := range gen(ctx) {
  19. fmt.Println(n)
  20. if n == 5 {
  21. break
  22. }
  23. }
  24. // Output:
  25. // 1
  26. // 2
  27. // 3
  28. // 4
  29. // 5

func WithDeadline

  1. func WithDeadline(parent Context, d .Time) (, CancelFunc)

WithDeadline 返回拥有截止时间的 parent 副本。如果 parent 的截止时间早于 d 将使用 parent 的截止时间。不管到达截止时间,调用取消函数,关闭 parent 的 Done channel 哪个先发生,Done channel 都会关闭。

取消 context 会释放和他关联的资源,所以我们应该在 Context 完成后立即取消它。


例:

  1. func WithTimeout(parent Context, timeout .Duration) (, CancelFunc)

withTimeout 也可以用 WithDeadline(parent, time.Now().Add(timeout)) 代替。

context 的取消操作会释放相关的资源,所以在 Context 完成以后就要调用 cancel:

  1. ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
  2. defer cancel() // 如果在超时之前完成就释放资源。
  3. return slowOperation(ctx)
  4. }


例:

  1. // 通过一个带超时的 context 可以通知阻塞函数应该在到达超时时间后放弃当前工作。
  2. ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
  3. defer cancel()
  4. select {
  5. case <-time.After(1 * time.Second):
  6. fmt.Println("overslept")
  7. case <-ctx.Done():
  8. fmt.Println(ctx.Err()) // 打印 "context deadline exceeded"
  9. }
  10. // Output:

func WithValue

  1. func WithValue(parent Context, key, val interface{})

WithValue 以 key 为键将 value 保存在 parent 的副本中并返回。

仅在请求声明周期中使用 Values,而不是将其作为函数的可选参数。

为了避免不同的包使用时出现相同的键,key 必须是可比较的并且不能是内置的 string 等类型。 WithValue 的用户应该定义自己的 key 类型。为了避免分配成 interface{},context 的 key 一般都为具体类型。或者导出的 context 的 key 变量的静态类型应该是一个指针或者接口。


例: