context标准库
context 翻译成中文是 上下文 的意思,它可以对 API 和进程之间传递截止日期、取消信号和其他请求范围的值。
使用上下文的程序应遵循以下规则:
- 保持包之间的接口一致
- 不要在结构类型中存储上下文
- 上下文应该是第一个参数,通常命名为ctx
- 上下文值仅用于传输进程和API的请求范围数据,而不用于向函数传递可选参数
context 是 Golang 开发常用的并发编程技术。
Context 实际上只定义了接口,凡是实现该接口的类都可称为是一种 context,官方包实现了几个常用的context,分别可用于不同的场景。
1. context类型
1.1 空 context
context包中定义了一个空的context,名为emptyCtx,用于 context 的根节点,空的 context 只是简单的实现了 context,本身不包含任何值,仅用于其他 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 any) any { return nil }
func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
|
1.2 CancelFunc 类型原型
1 2 3 4
|
type CancelFunc func()
|
1.3 Context 类型原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type Context interface { Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any }
|
Context 一般携带一个截止日期、一个取消信号和其他跨越API边界的值。上下文的方法可以被多个 goroutine 同时调用。
1.4 Background() 方法原型
1 2 3 4
| var background = new(emptyCtx) func Background() Context{ return background }
|
Background 函数返回一个非nil的空Context。它永远不会被取消,没有价值,也没有期限。它通常由主函数、初始化和测试使用,并作为传入请求的顶级上下文。
1.5 TODO() 方法原型
1 2 3 4
| var todo = new(emptyCtx) func TODO() Context { return todo }
|
TODO 函数返回一个非nil的空Context。代码应该使用上下文。当不清楚要使用哪个Context或者它还不可用时(因为周围的函数还没有扩展到接受Context参数)。
1.6 WithValue() 方法原型
1
| func WithValue(parent Context, key, val any) Context
|
WithValue 函数,返回父对象的副本,其中与键关联的值为val。
上下文值只用于传递进程和api的请求范围内的数据,而不是传递可选参数给函数。
提供的键必须具有可比性,不应该是string类型或任何其他内置类型,以避免使用上下文的包之间的冲突。使用WithValue的用户应该定义自己的键类型。在给接口{}
赋值时,为了避免分配,上下文键通常有具体的类型struct{}
。另外,导出的上下文关键变量的静态类型应该是指针或接口。
这个例子演示了使用 context.WithValue()
函数,如何将值传递给上下文,以及如果值存在,如何检索它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "context" )
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) } key1 := favContextKey("key1") ctx := context.WithValue(context.Background(), key1, "Golang") f(ctx, key1) f(ctx, favContextKey("key2")) }
|
2. context函数
2.1 WithCancel() 函数原型
1 2 3 4 5 6 7 8
| 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) } }
|
WithCancel 函数,返回带有新的 Done()
通道的父进程的副本。当返回的 cancel 函数被调用或父上下文的 Done()
通道被关闭时,返回上下文的 Done()
通道将被关闭,以哪个先发生为准。
取消此上下文将释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用cancel。
示例
。
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
| package main
import ( "context" "fmt" "time" )
func handelrequest(ctx context.Context) { go writeredis(ctx) go writedatabase(ctx) for { select { case <-ctx.Done(): fmt.Println("handelrequest done.") return default: fmt.Println("handelrequest running") time.Sleep(2 * time.Second) } } }
func writeredis(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("writeredis done.") return default: fmt.Println("writeredis running") time.Sleep(2 * time.Second) } } }
func writedatabase(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("writedatabase done.") return default: fmt.Println("writedatabase running") time.Sleep(2 * time.Second) } } }
func main() { ctx, cancel := context.WithCancel(context.Background()) go handelrequest(ctx)
time.Sleep(5 * time.Second) fmt.Println("it's time to stop all sub goroutines!") cancel()
time.Sleep(5 * time.Second) }
|
2.2 WithDeadline() 函数原型
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
| 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) { return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: d, } propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded) 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) } }
|
WithDeadline 函数,返回父上下文的一个副本,其截止日期调整为不迟于d。如果父上下文的截止日期已经早于d, WithDeadline(parent, d)
在语义上等价于parent。当截止日期到期、调用返回的 cancel 函数或父上下文的 Done()
通道被关闭时,返回上下文的Done通道将被关闭,以先发生的情况为准。
取消此上下文将释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用cancel。
示例
这个例子传递了一个带有任意截止日期的上下文,告诉阻塞函数一旦到达该时间就应该结束它的工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main
import ( "fmt" "context" "time" )
const shortDuration = 1 * time.Millisecond
func main() { d := time.Now().Add(shortDuration) ctx, cancel := context.WithDeadline(context.Background(), d) defer cancel()
select { case <-time.After(1 * time.Second): fmt.Println("在截止时间之后停止") case <-ctx.Done(): fmt.Println("在截止时间停止") } }
|
2.3 WithTimeout() 函数原型
1
| func WithTimeout(parent Context, timeout time.Duration) (context, CancelFunc)
|
WithTimeout函数,返回 WithDeadline(parent, time.Now().add(timeout))
取消这个上下文会释放与之相关的资源,所以只要在这个上下文中运行的操作完成,代码就应该调用cancel:
1 2 3 4 5
| func slowOperationWithTimeout(ctx context.Context) (Result, error) { ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() return slowOperation(ctx) }
|
示例
这个例子传递了一个带有超时的上下文,告诉阻塞函数在超时过后应该放弃它的工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "context" "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("在超时时间之后结束") case <-ctx.Done(): fmt.Println("在超时时间结束") } }
|