version: 1.10

package context

import "context"

概述

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:

  1. func DoSomething(ctx context.Context, arg Arg) error {
  2. // ... use ctx ...
  3. }

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

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

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

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

索引

Examples

Package files

context.go

Variables

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

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

  1. var DeadlineExceeded error = deadlineExceededError{}

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

type CancelFunc

  1. type CancelFunc func()

CancelFunc 会通知一个作业终止它的工作。CancelFunc 不会等待工作完成而立即终止它,在首次调用 CancelFunc 后,再调用 CancelFunc 不做任何事。

type Context

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

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

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

func Background

  1. func Background() Context

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

func TODO

  1. func TODO() Context

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

func WithCancel

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

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

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

例:

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

func WithDeadline

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

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

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

例:

  1. d := time.Now().Add(50 * time.Millisecond)
  2. ctx, cancel := context.WithDeadline(context.Background(), d)
  3. // 即使 ctx 将要过期,在最后调用取消函数也是很好的做法。不这么做会导致 context 和它的父级在不必要的时候存在。
  4. defer cancel()
  5. select {
  6. case <-time.After(1 * time.Second):
  7. fmt.Println("overslept")
  8. case <-ctx.Done():
  9. fmt.Println(ctx.Err())
  10. }
  11. // Output:
  12. // context deadline exceeded

func WithTimeout

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

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

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

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

例:

  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:
  11. // context deadline exceeded

func WithValue

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

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

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

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

例:

  1. type favContextKey string
  2. f := func(ctx context.Context, k favContextKey) {
  3. if v := ctx.Value(k); v != nil {
  4. fmt.Println("found value:", v)
  5. return
  6. }
  7. fmt.Println("key not found:", k)
  8. }
  9. k := favContextKey("language")
  10. ctx := context.WithValue(context.Background(), k, "Go")
  11. f(ctx, k)
  12. f(ctx, favContextKey("color"))
  13. // Output:
  14. // found value: Go
  15. // key not found: color