一、context是什么
context是标准库的接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
Deadline方法
设置context.Context的取消时间,即截止时间
Done方法
返回一个只读channel,当context被取消或者到达截止时间,channel就会被关闭,表示context的链路结束,多次调用Done方法会返回同一个channel
Err方法
返回context.Context结束的原因,它只会在Done返回的Channel被关闭时,才会返回非空的值,返回值有以下两种情况
- 如果是context.Context被取消,返回Cancelled
- 如果是context.Context超时,返回DeadlineExceeded
Value方法
从context.Context中获取键对应的值
对于同一个Context,多次调用Value并传入相同的Key会返回相同的结果,如果没有对应的key,则返回nil
键值对是通过WithValue方法写入的
二、context创建
1、根context创建
有两种方式创建根context
- context.Background()
- context.TODO()
根context是一个空的context
如果当前函数没有context作为入参,又需要context来传递上下文,一般会创建一个根context,作为起始的上下文往下传递
2、根context传递上下文
根context没有实质功能,为了能够传递上下文,需要依赖于With系列函数
with系列函数会派生出新的context(创建出新的context)
主要的With系列函数如下
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
基于当前context,每个with函数都会创建出一个新的context,这样类似于我们熟悉的树结构,当前context称为父context,派生出的新context称为子context。就像下面的context树结构:
通过根context,通过四个with系列方法可以派生出四种类型的context,每种context又可以通过同样的方式调用with系列方法继续向下派生新的context,整个结构像一棵树。
三、context作用
context主要有两个用途
- 上下文信息传递
- 用于并发控制,控制协程优雅退出
上下文信息传递
context中可以携带多个键值对,用于上下文信息的传递,例如请求id,以及trace_id等,用于链路追踪,以及配置透传
package main
import (
"context"
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
ctx := context.WithValue(context.Background(), "age", 18)
ctx = context.WithValue(ctx, "name", "zhang")
wg.Add(1)
go func(ctx context.Context) {
defer wg.Done()
fmt.Printf("name: %v\n", ctx.Value("name").(string))
fmt.Printf("age: %v\n", ctx.Value("age").(int))
}(ctx)
wg.Wait()
}
输出如下
name: zhang
age: 18
并发控制
context.WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
返回的cancel执行后,ctx.Done方法返回的channel会收到信号,示例如下
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
wg.Add(2)
go Watch(ctx, "goroutine1")
go Watch(ctx, "goroutine2")
time.Sleep(4 * time.Second)
fmt.Println("cancel watching")
cancelFunc()
wg.Wait()
}
func Watch(ctx context.Context, name string) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println(name, " exit!")
return
default:
fmt.Println(name, " waiting.")
time.Sleep(1 * time.Second)
}
}
}
context.WithDeadline
在WithCancel的基础上可以设置绝对时间来控制取消,到了指定的时间相当于会自动执行cancel函数
ctx, cancel := context.WithDeadline(context.Background(),time.Now().Add(4*time.Second))
context.WithTimeout
在WithCancel的基础上可以设置相对时间来控制取消,超过一定时间后,会自动执行cancel函数,Done方法返回的channel会收到一个信号
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)