golang中的context

位于src/context

包含文件 用途
benchmark_test.go 名字以Benchmark开头的函数,用于性能测试
context.go context源码,golang1.17中共567行
context_test.go context.go的测试用例函数
example_test.go context的使用示例
net_test.go 与net包配合测试
x_test.go 用于执行context_test.go的测试用例

简单示例

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
import (
"context"
"fmt"
"time"
)

func main() {
handleTime := 500 * time.Millisecond // handle函数执行时间
timeLimit := 1 * time.Second // context设置超时时间

ctx, cancel := context.WithTimeout(context.Background(), timeLimit)
defer cancel()

go handle(ctx, handleTime)

<-ctx.Done()
fmt.Println("main", ctx.Err())
}

func handle(ctx context.Context, handleTime time.Duration) {
select {
case <-ctx.Done():
fmt.Println("handle", ctx.Err())
case <-time.After(handleTime):
fmt.Println("process request with", handleTime)
}
}

当函数执行小于超时时间时,输出:

1
2
process request with 500ms
main context deadline exceeded

当函数执行大于超时时间时,输出有三种情况:

1
2
3
4
5
6
7
8
9
10
# 结果1
main context deadline exceeded
handle context deadline exceeded

# 结果2
handle context deadline exceeded
main context deadline exceeded

# 结果3
main context deadline exceeded

多个goroutine可以接收同一个Context的消息.

总体结构

关键属性

名称 类型 概述
Context 公共接口 context的核心,为事件定义了四个方法
deadlineExceededError 私有结构体 实现Error接口,用于表示上下文已到达deadline
emptyCtx 类型别名 int的别名,同时实现了Context接口
canceler 公共接口 定义了取消和完成方法
cancelCtx 私有结构体 实现canceler接口,组合了Context匿名接口
timerCtx 私有结构体 组合了cancelCtx
CancelFunc 类型别名 无參无返回值的函数别名,用于取消上下文
propagateCancel 函数 用于传递取消信号

其它属性

名称 类型 概述
Canceled 公有变量 error接口的实例,用于表示上下文已取消
DeadlineExceeded 公有变量 deadlineExceededError接口的实例
background 私有变量 类型为emptyCtx
todo 私有变量 background完全一致,只有名称上的差别

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
type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}

func (*emptyCtx) Done() <-chan struct{} {
return nil
}

func (*emptyCtx) Err() error {
return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}

func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}

emptyCtx没有值, 没有deadline, 只是用来当一个唯一的地址

1
2
3
4
5
6
7
8
9
10
11
12
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)

func Background() Context {
return background
}

func TODO() Context {
return todo
}

由此可见Background()和TODO()其实都是一个空的、非nil的Context


四大With函数

WithCancel

WithCancel returns a copy of parent with a new Done channel. The returned context's Done channel is closed when the returned cancel function is called or when the parent context's Done channel is closed, whichever happens first.
Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

1
2
3
4
5
6
7
8
9
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}

WithDeadline

WithDeadline returns a copy of the parent context with the deadline adjusted to be no later than d. If the parent's deadline is already earlier than d,WithDeadline(parent, d) is semantically equivalent to parent. The returned context's Done channel is closed when the deadline expires, when the returned cancel function is called, or when the parent context's Done channel is closed, whichever happens first.

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

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
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}

WithTimeout

WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete:

1
2
3
4
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}

WithValue

WithValue returns a copy of parent in which the value associated with key is val.
Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys. To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}.
Alternatively, exported context key variables' static type should be a pointer or interface.

1
2
3
4
5
6
7
8
9
10
11
12
func WithValue(parent Context, key, val interface{}) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}

和goroutine配合使用

References

context package
学会使用context取消goroutine执行的方法
Golang 并发编程之Context