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()) }
|