1.什么是上下文?
从Go 1.7
开始,标准库引入context
(上下文),他主要用来在goroutine
之间传递上下文信息(同步信号、超时时间、截止时间、Key-Val
值对)。
2. 为什么要用上下文?
2.1 场景说明
在实际开发过程中,我们往往会在main.goroutine(主协程)
中开启N 个goroutine(子协程)
来处理其他逻辑,那么主协程
和 子协程
之间信号怎么同步呢?列举以下几个需要信号同步的场景。
- 当
主协程
报错或者因为其他原因需要取消时,需要通知子协程
取消任务。
- 创建
子协程
时,希望指定超时时间或截止时间后,自动取消任务。
2.2 怎么解决?
context
包提供了一些函数,协助用户从现有的 Context
对象创建新的 Context
对象。
这些 Context
对象形成一棵树:当一个 Context
对象被取消时,继承自它的所有 Context
都会被取消。
Background
是所有 Context
对象树的根,它不能被取消。
3. 上下文接口
context
包中定义了一个接口context.Context
,具体定义代码如下:
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
|
4. 创建父上下文
4.1 签名及说明
源码
func Background() Context { return background } func TODO() Context { return todo }
|
context
包中最常用的方法是 context.Background、context.TODO
,这两个方法都会返回预先初始化好的私有变量 background 和 todo
,使用区别如下:
Background
: 主要用于main
函数、初始化以及测试代码中,作为最顶层的Context
,也就是根Context
。
TODO
: 当我们不知道该使用什么类型的Context
的时候,可以使用这个。
4.2 使用
func main() { fmt.Printf("Begin:%s\n",time.Now()) timeoutCtx, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second) defer cancelFunc() go func(ctx context.Context) { fmt.Println("子协程输出...") }(timeoutCtx) <-timeoutCtx.Done() fmt.Printf("等待超时结束:%s \n",time.Now()) }
|
5. 创建子上下文
5.1 可取消的(WithCancel
)
1.签名
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
|
context.WithCancel
函数能够从 context.Context
中衍生出一个新的子上下文并返回用于取消该上下文的函数。一旦我们执行返回的取消函数,当前上下文以及它的子上下文都会被取消,所有的 Goroutine 都会同步收到这一取消信号。
2.使用
func main() { fmt.Printf("Begin:%s\n",time.Now()) cancelCtx, cancelFunc := context.WithCancel(context.Background()) go func(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("子协程终止,上下文已被取消") return default: time.Sleep(1 * time.Second) fmt.Printf("%s: 子协程输出\n",time.Now()) } } }(cancelCtx) time.Sleep(3 * time.Second) cancelFunc() time.Sleep(1 * time.Second) fmt.Printf("End:%s \n",time.Now()) }
|
5.2 指定超时时间(WithTimeout
)
1.签名
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
|
context.WithTimeout
函数接收父上下文(parent
)和一个超时时间,能够从 context.Context
中衍生出一个新的子上下文并返回用于取消该上下文的函数。执行取消函数时功能和WithCancel
一样。
2.使用
func main() { waitTimeOut() subSendCancel() }
func waitTimeOut() { fmt.Printf("等待超时——> Begin:%s\n", time.Now()) ctx, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second) defer cancelFunc() go func(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("子协程收到取消信号,终止") return default: time.Sleep(1 * time.Second) fmt.Printf("子协程输出:%s\n", time.Now()) } } }(ctx) <-ctx.Done() time.Sleep(2 * time.Second) fmt.Printf("End:%s \n", time.Now()) }
func subSendCancel() { fmt.Printf("子协程主动发送取消信号——> Begin:%s\n", time.Now()) cancelCtx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second) go func(cancelFunc context.CancelFunc) { fmt.Println("子协程发送->取消信号") cancelFunc() }(cancelFunc) <-cancelCtx.Done() fmt.Printf("End:%s \n", time.Now()) }
|
5.3 指定截止时间(WithDeadline
)
1.签名
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
|
context.WithDeadline
函数接收父上下文(parent
)和一个截止时间,能够从 context.Context
中衍生出一个新的子上下文并返回用于取消该上下文的函数。执行取消函数时功能和WithCancel
一样。
2.使用
func main() { fmt.Printf("Begin:%s\n", time.Now()) deadlineCtx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second)) defer cancelFunc() go func(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("子协程终止,上下文已被取消") return default: time.Sleep(1 * time.Second) fmt.Printf("子协程输出: %s\n",time.Now()) } } }(deadlineCtx) <-deadlineCtx.Done() time.Sleep(1 * time.Second) fmt.Printf("End:%s \n", time.Now()) }
|
6.上下文中传值
6.1 签名
func WithValue(parent Context, key, val interface{}) Context
|
context.WithValue
能从父 Context
中创建一个子子 Context
,并传体一个键值对信息给子 Context
,在子 Context
中,通过context.Value
获取对应的值信息。
6.2 使用
func main() { fmt.Printf("Begin:%s\n", time.Now()) parentCtx, cancelFunc := context.WithCancel(context.Background()) valueCtx := context.WithValue(parentCtx, "name", "张三") go func(ctx context.Context,cancelFunc2 context.CancelFunc) { fmt.Printf("取出上下文中的name: %v\n",ctx.Value("name")) cancelFunc() }(valueCtx,cancelFunc) <- parentCtx.Done() fmt.Printf("End:%s \n", time.Now()) }
|