1. 简介
首先,先看一下 Golang 博客上关于 Context 的描述,点击查看原文 。
在 Go 服务中,每一个请求都是在它自己的 Goroutine 中处理,每一个处理通常又会启动额外的 Goroutine 来访问后端服务,如数据库或RPC服务等,处理请求的 Goroutine 集通常需要访问特定于请求的值,例如最终用户的身份、授权令牌和请求的截止日期。当一个请求被取消或者超时,所有处理该请求的 Goroutine 都应该快速的退出,以便系统可以回收它们正在使用的任何资源。
在 Google,我们开发了一个 context 包,可以轻松地将请求范围的值、取消信号和截止日期跨API边界传给处理请求所涉及的所有 Goroutine,这个包以 context 包公开可用。
简单的总结,context 包提供了上下文,可以携带值、取消信号以及截止日期。
官方文档中对 Context 的使用有一些说明和建议:
-
传入请求应当创建一个 Context,对服务的传出调用应该接收一个 Context
-
函数调用链之间必须传递 Context,可使用 WithDeadline、WithTimeout、WithCancel 或 WithValue 创建的修改副本
-
不要将 Context 存储在结构类型中,应当显示传递给需要它的每一个函数
-
Context 应该是第一个参数,通常命名为 ctx
-
不要传递 nil Context,即便函数允许,如果不确定需要传递哪个 Context,请传递 context.TODO
-
仅将上下文值用于传递进程或Api进程间的请求范围数据,不要用于给函数传递可选参数
-
相同的 Context 可以传递给不同 Goroutine 中运行的函数,Context 在多个 Goroutine 中使用是安全的
2. 源码
2.1 变量
context 包定义了两个变量:
-
context.Canceled:当上下文被取消时返回的错误
var Canceled = errors.New("context canceled")
-
context.DeadlineExceeded: 当上下文的最后期限已过时返回的错误
var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "context deadline exceeded" } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true }
2.2 类型
2.2.1 Context
context 包的核心是 Context 接口,它仅包含四个函数,如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
-
Deadline() (deadline time.Time, ok bool)
Deadline 返回代表此上下文完成的工作应该被取消的时间,没有设置截止日期的时候,截止日期返回 ok == false,对 Deadline 的连续调用返回相同的结果。
-
Done() <-chan struct{}
当代表此上下文完成的工作应该被取消时,Done 返回一个关闭的通道,如果这个上下文永远不会被关闭,Done 可能会返回 nil。
对Done 的连续调用会返回相同的结果,Done 通道的关闭有可能会异步发生,在取消函数返回之后。
Done 通道关闭的时机:
- WithCancel: 调用 cancel 时关闭
- WithDeadline: 截止日期到期时关闭
- WithTimeout: 超时结束时关闭
Done 用于在 select 语句中使用,如下:
// DoSomething 逻辑处理 func DoSomething(ctx context.Context) (int, error) { // 此处忽略逻辑 return 0, nil } // Demo 示例 func Demo(ctx context.Context, out chan<- int) error { for { v, err := DoSomething(ctx) if err != nil { return err } select { case <-ctx.Done(): return ctx.Err() case out <- v: } } }
Demo 函数循环调用 DoSomething 方法,如果 DoSomething 返回错误或者 ctx.Done 是关闭的话,则返回错误。
-
Err() error
如果 Done 没有被关闭,则 Err 返回 nil,否则返回一个用于说明为什么的非 nil 错误:
-
context.Canceled:上下文被关闭
-
context.DeadlineExceeded:上下文的最后期限已过
在 Err 返回非 nil 错误后,对 Err 的连续调用将会返回相同的错误。
-
-
Value(key interface{}) interface{}
Value 返回上下文中与 key 关联的值,如果没有则返回 nil,使用相同的 key 连续调用将返回相同的结果。
仅将上下文值用于传递进程或Api进程间的请求范围数据,不要用于给函数传递可选参数。
key 标识上下文中的特定值,希望在 Context 中存储值的函数通常会在全局变量中分配一个 key,然后将这个 key 用于 context.WithValue 以及 Context.Value。
key 可以定义为任何类型,包应该将 key 定义为未导出类型以避免冲突。
定义上下文 key 的包应该为使用该 key 的值定义一个类型安全的访问器,如下:
package demo import ( "context" "time" ) // Token 凭证 type Token struct { value string deadline time.Time userID int64 } // key key 是该包中定义的为导出的类型,这样可以避免与其它包冲突 type key int // tokenKey 上下文中 demo.Token 关联的 key,也是未导出的 var tokenKey key // NewContext 返回 demo 包的上下文,该上下文携带了 token func NewContext(ctx context.Context, token *Token) context.Context { return context.WithValue(ctx, tokenKey, token) } // FromContext 从上下文中取值 func FromContext(ctx context.Context) (*Token, bool) { v, ok := ctx.Value(tokenKey).(*Token) return v, ok }
以上示例代码来自于官方的说明,通过在保内定义指定 key,并且限定为不导出,再通过 NewContext 和 FromContext 方法为该 key 提供了安全的访问方式。
2.2.2 CancelFunc
CancelFunc 告诉操作放弃它的工作,它不会等待工作停止,一个 CancelFunc 可能会被多个 Goroutine 同时调用,当第一次调用后,后续对它的调用将什么也不会做。
2.2.3 emptyCtx
空上下文没有永远不会被取消,没有值,没有截止日期,它也不是结构体 struct{},因为这种类型的变量必须有不同的地址。
emptyCtx 实现了 Context 接口 和 Stringer 接口,Background 和 TODO 类型的上下文都是 emptyCtx。
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
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"
}
2.2.4 canceler
canceler 是一种上下文类型,可以直接被取消,有两种实现:*cancelCtx 和 *timerCtx
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
- cancel:取消方法,可以指定是否从父级上下文中移除
- Done:返回一个关闭的通道
2.2.5 cancelCtx
cancelCtx 可以被取消,当被取消的时候,它还会取消任何实现 cancel 的子级.
cancelCtx 的几个成员说明:
- mu:互斥锁,保护下面几个成员
- done:done 通道,惰性创建(调用 Done() 时创建),第一次调用 cancel 方法的时候关闭
- children:子级,第一次调用 cancel 方法的时候设置为 nil
- err:错误,第一次调用 cancel 方法的时候设置为非 nil
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
// &cancelCtxKey is the key that a cancelCtx returns itself for.
var cancelCtxKey int
func (c *cancelCtx) Value(key interface{}) interface{} {
if key == &cancelCtxKey {
return c
}
return c.Context.Value(key)
}
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
func (c *cancelCtx) String() string {
return contextName(c.Context) + ".WithCancel"
}
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {
close(closedchan)
}
// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
// 取消
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
// 如果传入的错误为空,则返回 panic
if err == nil {
panic("context: internal error: missing cancel error")
}
// 如果上下文的错误不为空,说明已经取消过,直接返回即可
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
// 将错误赋值到上下文
c.err = err
// 由于 done 是在调用 Done 的时候才创建,所以此处 done 有可能为 nil
// 如果是 nil,则将 done 赋值为一个已经关闭的 channel
// 如果不是 nil,则关闭这个 channel
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
// 依次取消子级
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
// 清除子级 map
c.children = nil
c.mu.Unlock()
// 如果需要从父级移除,则调用 removeChild 从父级中清理掉自己
if removeFromParent {
removeChild(c.Context, c)
}
}
2.2.6 timerCtx
timerCtx 携带了一个 timer 和一个截止时间,嵌入了一个 cancelCtx 来实现 Done 和 Err,通过停止计时器然后委托 cancelCtx 来实现 cancel。
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
c.deadline.String() + " [" +
time.Until(c.deadline).String() + "])"
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
// 调用 cancelCtx 的 cancel
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
// 如果 timer 不为 nil,则停止计时器
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
2.2.7 valueCtx
valueCtx 会携带一个键值对,逻辑简单,实现了 Value 函数。
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) String() string {
return contextName(c.Context) + ".WithValue(type " +
reflectlite.TypeOf(c.key).String() +
", val " + stringify(c.val) + ")"
}
func (c *valueCtx) Value(key interface{}) interface{} {
// 如果传入的 key 与上下文的 key 相同,则返回值
if c.key == key {
return c.val
}
// 如果不相同,则从父级查询
return c.Context.Value(key)
}
2.3 函数
2.3.1 func Background() Context
Background 返回了一个非 nil 的空上下文,它永远不会被取消,没有存储值也没有最后期限,通常由主函数、初始化及测试使用,并且作为请求的顶级上下文使用。
Background 本质上是 emptyCtx。
var (
background = new(emptyCtx)
)
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
2.3.2 func TODO() Context
TODO 返回一个非 nil 的空上下文,当不清楚需要使用何种 Context 或者它尚不可用时(周边函数尚未扩展为接收 Context 参数)。TODO 会被静态分析工具识别,这些工具确定上下文是否在程序中正确传播。
TODO 本质上是 emptyCtx。
var (
todo = new(emptyCtx)
)
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
return todo
}
2.3.3 func WithValue(parent Context, key, val interface{}) Context
WithValue 返回了一个基于父级并携带一个键值对的副本,需要注意几点:
- 仅将上下文值用于传递进程或Api进程间的请求范围数据,不要用于给函数传递可选参数
- key 需要是可以比较的类型,并且不应该是字符串类型或任何内置类型
- 使用 WithValue 应该定义自己的 key 类型
- 为避免在分配给 interface{} 时进行分配,上下文键通常具有具体类型 struct{}
- 导出的上下文键变量的静态类型应该是指针或接口。
// 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.
func WithValue(parent Context, key, val interface{}) Context {
// 检测父级
if parent == nil {
panic("cannot create context from nil parent")
}
// 检测key
if key == nil {
panic("nil key")
}
// 检测key是否是可比较类型
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
// 基于父级创建 valueCtx
return &valueCtx{parent, key, val}
}
2.3.4 func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel 返回一个基于父级上下文并携带一个新的 Done 通道的上下文,当返回的取消函数被调用或者父级的 Done 通道被关闭时,返回的 Done 通道会被关闭,这两种情景以先发生者为准。
取消上下文会释放与其关联的资源,因此代码应在上下文执行的操作完成后立即调用取消方法。
// 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.
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) }
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
// propagateCancel arranges for child to be canceled when parent is.
// 传播取消,负责保证在父级取消的时候,可以正确传播到子级
func propagateCancel(parent Context, child canceler) {
// 此处的 parent 可以是任意类型的上下文,如果是 emptyCtx 类型,则 parent.Done() 返回的永远是 nil
// 如果是 cancelCtx 或者 timerCtx,返回的不会是 nil
// 如果不为 nil,说明永远不会取消,也就不需要一层一层通知子级取消
done := parent.Done()
if done == nil {
return // parent is never canceled
}
// 判断 done channel 是否可以读取,可以读取则表明父级已取消,则需要取消子级
select {
case <-done:
// parent is already canceled
child.cancel(false, parent.Err())
return
default:
}
// 获取父级的底层cancelCtx
if p, ok := parentCancelCtx(parent); ok {
// 如果获取到,则进行取消
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
// 如果父级已经取消,则直接取消子级即可
child.cancel(false, p.err)
} else {
// 父级没有取消
// 如果子级为 nil,则需要在父级的 children 里关联子级
// 这样当父级取消的时候,会依次取消父级 children 里面关联的所有子级
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 这里有多种情况:
// 1. 父级已取消
// 2. 父级永远不会取消
// 3. 无法转换为标准 cancelCtx
// 4. 是一个自定义 Done 的 cancelCtx
// 前两种情况在函数开头已经判断处理,此处无需处理
// 后两种情况无法通过父级的 children map 来取消,因此启动一个 Goroutine 来处理
atomic.AddInt32(&goroutines, +1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
// parentCancelCtx returns the underlying *cancelCtx for parent.
// It does this by looking up parent.Value(&cancelCtxKey) to find
// the innermost enclosing *cancelCtx and then checking whether
// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
// 获取父级的底层cancelCtx
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
// 如果父级的 done channel 为空,说明不支持 cancel
// 如果父级的 done channel 为 closedchan,说明父级已经取消
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
// 判断父级是否可以转换为 cancelCtx,如果无法转为,则返回 nil 和 false
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
// 如果可以转换,说明父级是 cancelCtx 类型或其衍生出来的上下文,比如 timerCtx 或者用户自定义的 cancelCtx
// 需要进一步判断 父级与提取出来的上下文的 done channel 是否一致
// 如果一致,说明就是标准的 cancelCtx 或 timerCtx,或自定义 cancelCtx 但没有重写 Done()
// 如果不一致,说明是自定义的 cancelCtx 并且 重写了 Done(),这种情况需要单独处理
p.mu.Lock()
ok = p.done == done
p.mu.Unlock()
if !ok {
return nil, false
}
return p, true
}
从以上代码可以看出,WithCancel 不能基于为 nil 的上下文来创建,创建的时候会先生成一个 cancelCtx,之后调用 propagateCancel 方法来传播取消。
propagateCancel 函数的目的是传播取消,它的大致流程如下:
- 判断父级是否会取消,如果不会取消则不执行任何操作
- 如果父级可取消,则读取父级的 done channel,如果取到说明父级已取消,此时需要取消对应传入的子级
- 如果父级当前未取消,则需要从父级提取底层的 cancelCtx,观察是否是标准的 cancelCtx 或者是自定义但没有覆盖 Done() 方法,如果是,则判断父级是否取消,如果取消则取消传入的子级;如果没有取消,则将传入的子级加入父级的 children,这样当父级取消的时候也会自动取消子级
- 如果父级底层是自定义 Done 的 cancelCtx,则启动 Goroutine 来完成取消操作
2.3.5 func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
WithDeadline 返回基于父级的副本,其最后期限调整为不迟于传入的时间,如果父级的截止日期早于传入的时间,本质上是返回一个 cancelCtx。
上下文的 done channel 会在三种情况下关闭,这三种情况以先发生者为准:
- 到了截止时间
- 父级的 done channel 关闭
- 返回的取消函数被调用
// 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.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
// 判断父级是否支持 Deadline,如果支持并且截止时间早于传入的时间,意味着父级一定会在当前要创建的上下文前取消
// 这样,当父级被取消的时候,其关联的子级也会自动被取消
// 所以此处返回一个 cancelCtx 就可以了
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
// 创建一个 timerCtx
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) }
}
// 当前时间未到截止时间,则创建一个定时器,在定时器到时间收自动 cancel
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) }
}
2.3.6 func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout 本质上是调用的 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:
//
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
// defer cancel() // releases resources if slowOperation completes before timeout elapses
// return slowOperation(ctx)
// }
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
3. 示例
3.1 WithCancel
这个例子中,通过 WithCancel 获得一个 cancelCtx,之后启动协程,每隔一秒打印一句 hahaha,直到收到取消信号,主协程中,会在10秒后调用 cancel 函数。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
go Test(ctx)
time.Sleep(10 * time.Second)
cancel()
time.Sleep(1 * time.Second)
}
func Test(ctx context.Context) {
for range time.Tick(time.Second) {
select {
case <-ctx.Done():
log("context is canceled")
return
default:
log("hahaha")
}
}
}
func log(msgs ...interface{}) {
fmt.Println(time.Now().Format("2006-01-02 15:04:05"), msgs)
}
执行结果如下:
2022-05-07 10:02:33 [hahaha]
2022-05-07 10:02:34 [hahaha]
2022-05-07 10:02:35 [hahaha]
2022-05-07 10:02:36 [hahaha]
2022-05-07 10:02:37 [hahaha]
2022-05-07 10:02:38 [hahaha]
2022-05-07 10:02:39 [hahaha]
2022-05-07 10:02:40 [hahaha]
2022-05-07 10:02:41 [hahaha]
2022-05-07 10:02:42 [context is canceled]
3.2 WithDeadline
这个例子中,生成了一个10秒超时的上下文来调用 Test 函数,Test 函数每一秒都会打印 hahaha,直到 Context 超时,并打印错误信息。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
Test(ctx)
}
func Test(ctx context.Context) {
for range time.Tick(time.Second) {
select {
case <-ctx.Done():
log("context is canceled, err: ", ctx.Err())
return
default:
log("hahaha")
}
}
}
func log(msgs ...interface{}) {
fmt.Println(time.Now().Format("2006-01-02 15:04:05"), msgs)
}
执行结果:
2022-05-07 12:23:32 [hahaha]
2022-05-07 12:23:33 [hahaha]
2022-05-07 12:23:34 [hahaha]
2022-05-07 12:23:35 [hahaha]
2022-05-07 12:23:36 [hahaha]
2022-05-07 12:23:37 [hahaha]
2022-05-07 12:23:38 [hahaha]
2022-05-07 12:23:39 [hahaha]
2022-05-07 12:23:40 [hahaha]
2022-05-07 12:23:41 [hahaha]
2022-05-07 12:23:42 [context is canceled, err: context deadline exceeded]
3.3 WithTimeout
WithTimeout 本质上就是调用的 WithDeadline,这里的例子和 WithDeadline 基本一致,差别仅在于获取 Context 的地方:
# WithDeadline
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
# WithTimeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
3.4 WithValue
这个例子是简单的设置及获取 kv。
package main
import (
"context"
"errors"
"fmt"
)
type testKey string
func main() {
ctx := context.WithValue(context.Background(), testKey("a"), 1)
fmt.Println(GetValue(ctx, testKey("a")))
fmt.Println(GetValue(ctx, testKey("b")))
}
// GetValue 获取Value
func GetValue(ctx context.Context, key testKey) (interface{}, error) {
v := ctx.Value(key)
if v == nil {
return nil, errors.New(fmt.Sprintf("value of %s not found", key))
}
return v, nil
}
输出结果为:
1 <nil>
<nil> value of b not found