context

context

https://pkg.go.dev/context@go1.20.1

​ context包context 定义了 Context 类型,它在 API 边界和进程之间传递截止时间、取消信号和其他请求作用域值。

​ 服务端的传入请求应该创建一个 Context,对服务端的外部调用应该接受一个 Context。它们之间的函数调用链必须传播 Context,并且可以使用 WithCancel、WithDeadline、WithTimeout 或 WithValue 创建派生的 Context 来替换它。当一个 Context 被取消时,所有从它派生的 Context 也会被取消。

​ WithCancel、WithDeadline 和 WithTimeout 函数接受一个 Context(父级)并返回一个派生的 Context(子级)和一个 CancelFunc。调用 CancelFunc 会取消子级及其子级,移除父级对子级的引用并停止任何相关的定时器。如果不调用 CancelFunc,则会泄漏子级及其子级,直到父级被取消或定时器触发。go vet 工具检查所有控制流路径上是否使用了 CancelFuncs。

​ WithCancelCause 函数返回一个 CancelCauseFunc,它接受一个错误并将其记录为取消原因。调用取消的 Context 或任何其子级的 Cause 函数都会检索到取消原因。如果未指定原因,则 Cause(ctx) 返回与 ctx.Err() 相同的值。

​ 使用 Context 的程序应该遵循以下规则,以使接口在包之间保持一致并启用静态分析工具检查上下文传播:

(1)不要在结构类型中存储 Context;相反,将 Context 显式传递给需要它的每个函数。Context 应该是第一个参数,通常命名为 ctx:

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

​ (2)即使函数允许,也不要传递 nil Context。如果不确定要使用哪个 Context,请传递 context.TODO。

​ (3)仅将 context Value 用于跨进程和 API 传递的请求作用域数据,而不是将可选参数传递给函数。

​ (4)同一个 Context 可以传递给在不同 goroutine 中运行的函数;Context 可以同时被多个 goroutine 安全使用。

​ 有关使用 Context 的示例代码,请参见博客《Go并发模式:Context》

常量

This section is empty.

变量

View Source

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

​ Canceled是当上下文被取消时,Context.Err返回的错误。

View Source

1
var DeadlineExceeded error = deadlineExceededError{}

​ DeadlineExceeded是当上下文的截止时间过期时,Context.Err返回的错误。

函数

func Cause <- go1.20

1
func Cause(c Context) error

​ Cause函数返回一个非nil的错误,解释为什么c被取消。c或其父级的第一个取消设置原因。如果取消是通过对CancelCauseFunc(err)的调用进行的,则Cause返回err。否则,Cause(c)返回与c.Err()相同的值。如果c尚未被取消,则Cause返回nil。

func WithCancel

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

​ WithCancel函数返回parent的副本并创建一个新的Done通道。当调用返回的cancel函数或父级上下文的Done通道关闭时,返回的上下文的Done通道关闭。

​ 取消此上下文会释放与之相关联的资源,因此代码应尽快调用cancel,以便在此上下文中运行的操作完成。

WithCancel Example

​ 这个例子演示了使用可取消的上下文来防止 Goroutine 泄漏。在例子函数的结尾处,由 gen 启动的 Goroutine 将在不泄漏的情况下返回。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
	"context"
	"fmt"
)

func main() {
    // gen在一个单独的goroutine中生成整数,并将它们发送到返回的通道中。
	// gen的调用者在消耗完生成的整数后需要取消上下文,以免泄露gen启动的内部goroutine。
	gen := func(ctx context.Context) <-chan int {
		dst := make(chan int)
		n := 1
		go func() {
			for {
				select {
				case <-ctx.Done():
					return // 返回时不泄露goroutine的信息
				case dst <- n:
					n++
				}
			}
		}()
		return dst
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 当我们消耗完整数后再取消。

	for n := range gen(ctx) {
		fmt.Println(n)
		if n == 5 {
			break
		}
	}
}
Output:

1
2
3
4
5

func WithCancelCause <- go1.20

1
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)

​ WithCancelCause函数类似于WithCancel但返回一个CancelCauseFunc而不是CancelFunc。使用非nil错误(the “cause”)调用cancel将记录该错误在ctx中;然后可以使用Cause(ctx)检索它。使用nil调用cancel将原因设置为已取消。

例如使用:

1
2
3
4
ctx, cancel := context.WithCancelCause(parent)
cancel(myError)
ctx.Err() // returns context.Canceled
context.Cause(ctx) // returns myError

func WithDeadline

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

​ WithDeadline函数返回父Context的副本,其截止时间早于或等于d。如果父Context的截止时间早于d,则WithDeadline(parent, d)在语义上等同于parent。返回的Context的Done通道在到期时关闭,当调用返回的cancel函数时关闭,或者当父Context的Done通道关闭时关闭,以先发生的事件为准。

​ 取消此上下文会释放与其关联的资源,因此代码应在此Context中运行的操作完成后尽快调用cancel。

WithDeadline Example

此示例传递了一个带有任意deadline 的上下文,以告诉阻塞函数,它应该在到达deadline 后立即放弃它的工作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
	"context"
	"fmt"
	"time"
)

const shortDuration = 1 * time.Millisecond

func main() {
	d := time.Now().Add(shortDuration)
	ctx, cancel := context.WithDeadline(context.Background(), d)

	// 尽管ctx会过期,但在任何情况下调用其取消函数都是很好的做法。
    // 如果不这样做,可能会使上下文和它的parent活得比必要的时间更长。
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}

}

Output:

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
2
3
4
5
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
	ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
	defer cancel()  // 如果slowOperation在超时前完成,则释放资源
	return slowOperation(ctx)
}
WithTimeout Example

此示例传递了一个带有超时的上下文,以告诉阻塞函数在超时过后应该放弃它的工作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
	"context"
	"fmt"
	"time"
)

const shortDuration = 1 * time.Millisecond

func main() {
    //传递一个带有超时的上下文,以告诉阻塞函数在超时过后应该放弃它的工作
	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err()) // prints "context deadline exceeded"
	}

}

Output:

context deadline exceeded

类型

type CancelCauseFunc <- go1.20

1
type CancelCauseFunc func(cause error)

​ CancelCauseFunc类型的行为类似于CancelFunc,但还会设置取消原因。该原因可以通过在取消的Context或其派生的Context上调用Cause来检索。

​ 如果上下文已经被取消,则CancelCauseFunc不会设置取消原因。例如,如果childContext是从parentContext派生的:

  • 如果parentContext在childContext之前以cause1取消,则Cause(parentContext) == Cause(childContext) == cause1 (即 parentContext 可以影响到 childContext )
  • 如果childContext在parentContext之前以cause2取消,则Cause(parentContext) == cause1,并且Cause(childContext) == cause2。(即 childContext 影响不到 parentContext )

type CancelFunc

1
type CancelFunc func()

​ CancelFunc类型告诉操作放弃它的工作。CancelFunc类型不等待工作停止。多个goroutine可以同时调用CancelFunc。在第一次调用之后,对CancelFunc的后续调用不起作用。

type Context

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
type Context interface {
    // Deadline返回代表该上下文所做的工作应该被取消的时间。
    // 如果没有设置deadline,Deadline方法返回ok==false。
    // 对Deadline的连续调用会返回相同的结果。
	Deadline() (deadline time.Time, ok bool)

    // Done返回一个通道,该通道在此上下文代表的工作应被取消时关闭。
    // 如果这个上下文永远不能被取消,则Done可能会返回nil。
    // 对Done的连续调用会返回相同的值。
    // Done通道的关闭可以异步发生,在cancel函数返回之后。
	//
	// WithCancel安排在调用cancel时关闭Done;
	// WithDeadline安排在截止时间过期时关闭Done;
	// WithTimeout安排在超时时间过去时关闭Done。
	//
    // Done用于在select语句中使用:
	//
	//	// Stream用DoSomething生成数值,并将其发送到out,
    //  // 直到DoSomething返回错误或ctx.Done被关闭。
	//  func Stream(ctx context.Context, out chan<- Value) error {
	//  	for {
	//  		v, err := DoSomething(ctx)
	//  		if err != nil {
	//  			return err
	//  		}
	//  		select {
	//  		case <-ctx.Done():
	//  			return ctx.Err()
	//  		case out <- v:
	//  		}
	//  	}
	//  }
	//
    // 参见https://blog.golang.org/pipelines,
    // (2014年的博客:Go Concurrency Patterns: Pipelines and cancellation)
    // 以了解更多关于如何使用Done通道取消的例子。
	Done() <-chan struct{}

    // 如果Done尚未关闭,则Err返回nil。
	// 如果Done已关闭,则Err返回一个非nil错误,解释原因:
// 如果上下文被取消,则为Canceled;
// 如果上下文的截止时间过去,则为DeadlineExceeded。
// 在Err返回非nil错误之后,连续调用Err将返回相同的错误。
	Err() error

    // Value返回与此上下文相关的key的值,
    // 如果没有与key相关的值,则返回nil。
    // 用相同的key连续调用Value会返回相同的结果。
	//
	// 只有在请求范围内的数据穿越进程和API时才使用上下文值,
    // 而不是将可选参数传递给函数。
	//
    // key 标识了一个上下文中的特定值。
    // 希望在Context中存储值的函数通常会在一个全局变量中分配一个key,
    // 然后使用该key作为context.WithValue和Context.Value的实参。
    // key可以是任何支持可比较的类型。
	// 包应该将key定义为一个不可导出的类型,以避免冲突。
	//
	// 定义Context的key的包应该为使用该key存储的值提供类型安全的访问器:
	//	
	// // user包定义了一个存储在Contexts中的User类型。
	// 	package user
	//
	// 	import "context"
	//
	// // User是存储在Contexts中的值的类型。
	// 	type User struct {...}
	//
	//	// key是本包中定义的键的不可导出类型。
	//  //这可以防止与其他包中定义的键发生冲突。
	// 	type key int
	//
	//	// userKey是Contexts中user.User值的key。
    //  // 它是未被导出的;客户端使用user.NewContext和user.FromContext而不是直接使用这个key。
	// 	var userKey key
	//
	//	// NewContext返回一个新的带有u值的Context。
	// 	func NewContext(ctx context.Context, u *User) context.Context {
	// 		return context.WithValue(ctx, userKey, u)
	// 	}
	//
	//	// FromContext返回存储在ctx中的User值(如果有的话)。
	// 	func FromContext(ctx context.Context) (*User, bool) {
	// 		u, ok := ctx.Value(userKey).(*User)
	// 		return u, ok
	// 	}
	Value(key any) any
}

​ Context传递截止时间、取消信号和其他值跨API边界。

​ Context的方法可以同时被多个goroutine调用。

func Background

1
func Background() Context

​ Background函数返回一个非nil、空的Context。它永远不会被取消,没有值,也没有截止时间。它通常由main函数、初始化和测试以及作为传入请求的顶级Context使用。

func TODO

1
func TODO() Context

​ TODO函数返回一个非nil、空的Context。当不清楚使用哪个Context或尚未可用(因为周围的函数尚未扩展为接受Context参数)时,代码应使用context.TODO。

func WithValue

1
func WithValue(parent Context, key, val any) Context

​ WithValue函数返回parent的副本,其中与键关联的值为val。

​ 仅将context Values用于跨进程和API传递的请求范围数据,而不是将可选参数传递给函数。

​ 所提供的key 必须是可比较的,不应该是字符串或任何其他内置类型,以避免不同包之间的冲突。使用WithValue的用户应该为它们自己的key 定义类型。为了避免在分配给接口{}时分配内存,context键通常具有具体类型struct{}。或者,导出的上下文key 变量的静态类型应该是指针或接口。

WithValue Example
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
	"context"
	"fmt"
)

func main() {
	type favContextKey string

	f := func(ctx context.Context, k favContextKey) {
		if v := ctx.Value(k); v != nil {
			fmt.Println("found value:", v)
			return
		}
		fmt.Println("key not found:", k)
	}

	k := favContextKey("language")
	ctx := context.WithValue(context.Background(), k, "Go")

	f(ctx, k)
	f(ctx, favContextKey("color"))

}
Output:

found value: Go
key not found: color