Context
上下文 context.Context Go 语言中用来设置截止日期,同步信号,传递请求相关的结构体,context 与 gotoutine 有比较密切的关系,是 Go 语言中的独特设计。
Context 作用
- 每个 Context 都会从最顶层 Goroutine 一层一层传递到最下层,context.Context 可以在上层 Goroutine 执行出现错误,会将信号及时同步到下一次层,这样,上层因为某些原因失败时, 下层就可以停掉无用的工作,以减少资源损耗。实际应用:RPC 超时时间设置
- context 中一般意义 context.WithValue 能从父上下文中创建一个子上下文,传值的子上下文使用 context.valueCtx 类型。WithValue 是一对 kv 类型,可用来传值,实际应用:传递全局唯一的调用链
Context 接口
Context 是 Go 语言在 1.7 版本引入的标准库接口,有以下需要实现的方法
- Deadlime 返回 context.Context 被取消的时间,也就是完成工作的截止时间
- Done 返回一个 Channel ,这个 Channel 会在当前工作完成或者被取消后关闭,多次调用 Done 方法会返回同一个Channle
- Err 返回 context.Context 结束的原因,只会在 Done 方法对应的 Channel 关闭时返回非空的值
- Value 从 context.Context 中获取对应的值,对同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果。该方法可以用来传递特定的请求。
- 如果 context.Context 被取消,会返回 Canceled 错误;
- 如果 context.Context 超时,会返回 DeadlineExceeded 错误;
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
使用 context 同步信号
创建一个过期时间为 1s 的上下文, 并向上下文传入 handle 函数,该方法会使用 500ms 的时间处理传入的请求。
package contexttest import ( "context" "fmt" "testing" "time" ) func TestContext(t *testing.T) { // 创建一个过期时间为1s的上下文 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() go handle(ctx, 500*time.Millisecond) select { case <-ctx.Done(): fmt.Println("main", ctx.Err()) } } func handle(ctx context.Context, duration time.Duration) { select { case <-ctx.Done(): fmt.Println("handle", ctx.Err()) case <-time.After(duration): fmt.Println("process request with", duration) } }
运行结果:
=== RUN TestContext process request with 500ms main context deadline exceeded --- PASS: TestContext (1.00s) PASS
Context 创建
- 根 Context: 通过 context.Backgroud() 创建
- 子 Conetxt:context.WithCancel(parentConetxt) 创建
ctx, cancel := context.WithCancel(conetext.Background())
- 当前 Context 被取消时,其他的子 context 都会被取消
- 接收取消通知 <- ctx.Done()
测试代码package main
import ( "context" "testing" "time" ) func isCanceled(ctx context.Context) bool { select { case <-ctx.Done(): return true default: return false } } func TestCanceledByConetxt(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) for i := 0; i < 5; i++ { go func(i int, ctx context.Context) { for { if isCanceled(ctx) { break } } time.Sleep(time.Second * 1) }(i, ctx) } cancel() }