1. Context定义
context 是上下文的意思,它最大的作用就是在上下文中传递信息,在golang当中是一种接口类型,凡是实现该接口的类都可称为是一种 context,context里面有四个方法,将这四个方法全部实现就代表实现了context的一个类,它与 waitGroup 最大的不同点是 context 对于派生 goroutine 有更强的控制力,它可以控制多级的goroutine
2. Context四大用法
1. 数据传输(大部分时候用的都是context的这个用法
Go
type UserInfo struct {
Name string
}
func GetUser(ctx context.Context) {
// 从上下文提取数据并打印
fmt.Println(ctx.Value("name").(UserInfo).Name)
}
func main() {
ctx := context.Background()
// 向上下文添加键值对数据
ctx = context.WithValue(ctx, "name", UserInfo{
Name: "leyinlin",
})
GetUser(ctx)
}
流程:
-
在上下文中添加键值对数据(WithValue方法);
-
从上下文中获取数据(Value方法);两个步骤,简单易懂
2. 协程取消
Go
var wait = sync.WaitGroup{}
func main() {
// 记录开始时间
t1 := time.Now()
// 返回上下文和取消函数两个值,当调用该取消函数时,所有监听该上下文的协程都会被通知取消
ctx, cancel := context.WithCancel(context.Background())
// 计数
wait.Add(1)
go func() {
ip, err := GetIp(ctx)
if err != nil {
fmt.Println(err)
}
fmt.Println(ip)
}()
go func() {
time.Sleep(2 * time.Second)
// 取消协程
cancel()
}()
wait.Wait()
fmt.Println("执行完成", time.Since(t1))
}
func GetIp(c context.Context) (ip string, err error) {
//启动一个协程监听上下文的取消信号,当上下文被取消时,c.Done()通过会关闭
go func() {
select {
case <-c.Done():
fmt.Println("协程取消", c.Err())
err = c.Err()
wait.Done()
return
}
}()
time.Sleep(4 * time.Second)
ip = "192.168.200.1"
wait.Done()
return ip, nil
}
流程:
-
主协程启动两个子协程:一个用于获取 IP,另一个用于在 2 秒后触发取消。
-
获取 IP 的协程内部启动了一个监听协程,用于监听取消信号。
-
2 秒后,取消协程执行 `cancel()`,通知上下文被取消。
-
监听协程检测到上下文取消,设置错误并减少 `WaitGroup` 计数。
-
获取 IP 的协程在 4 秒后完成任务,设置 IP 并减少计数。
-
主协程在计数归零后继续执行,打印执行完成的时间。
3. 截止时间
Go
func main() {
var wg = sync.WaitGroup{}
t1 := time.Now()
// 返回新的上下文和取消函数两个值,上下文会在当前时间加上2秒后自动取消
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second))
wg.Add(1)
go func() {
ip, err := GetIp1(ctx, &wg)
if err != nil {
fmt.Println(err)
}
fmt.Println(ip)
}()
cancel()
wg.Wait()
fmt.Println("执行完成", time.Since(t1))
}
// GetIp1 协程 参数有带截止时间的上下文和 waitGroupfunc GetIp1(c context.Context, wg *sync.WaitGroup) (ip string, err error) {
go func() {
select {
// 当上下文被取消时,该通道会关闭,然后执行相应逻辑
case <-c.Done():
fmt.Println("协程取消", c.Err())
err = c.Err()
wg.Done()
return
}
}()
// 模拟获取ip耗时操作,这里休眠了4秒,但是截止时间是2秒,所以这个操作会超出截止时间,
// 导致上下文自动取消
time.Sleep(4 * time.Second)
ip = "192.168.200.1"
wg.Done()
return ip, nil
}
流程:
-
通过 `context.WithDeadline` 设置一个截止时间,超过这个时间上下文会自动取消。
-
`GetIp1` 函数中的任务需要在截止时间之前完成,否则上下文取消会触发取消机制。
-
任务协程内部启动了一个监听协程,用于监听上下文的取消信号,确保任务可以在截止时间到达时及时响应。
4. 超时时间
Go
func main() {
var wg = sync.WaitGroup{}
t1 := time.Now()
// 返回上下文和取消函数两个值,上下文会在2秒后自动取消
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
wg.Add(1)
// 启动协程执行 GetIp2函数,传递带超时的上下文和waitGroup
go func() {
ip, err := GetIp2(ctx, &wg)
if err != nil {
fmt.Println(err)
}
fmt.Println(ip)
}()
//取消上下文
cancel()
wg.Wait()
fmt.Println("执行完成", time.Since(t1))
}
func GetIp2(c context.Context, wg *sync.WaitGroup) (ip string, err error) {
go func() {
select {
// 协程被取消,打印错误
case <-c.Done():
fmt.Println("协程取消", c.Err())
err = c.Err()
wg.Done()
return
}
}()
// 模拟任务时长
time.Sleep(4 * time.Second)
ip = "192.168.200.1"
wg.Done()
return ip, nil
}
流程:
-
通过 `context.WithTimeout` 设置一个超时时间,上下文会在指定时间后自动取消。
-
`GetIp2` 函数中的任务需要在超时时间之前完成,否则上下文取消会触发取消机制。
-
任务协程内部启动了一个监听协程,用于监听上下文的取消信号,确保任务可以在超时时间到达时及时响应。
3. 超时时间和截止时间的区别
**时间类型:1.**超时时间是相对时间(从调用时开始计算)
- 截止时间是绝对时间(指定的未来时间)
**使用场景:1.**超时时间任务需要在调用后一定时间内完成
- 截止时间任务需要在指定时间之前完成
**总结:**超时时间适合用于任务需要在调用后一定时间内完成的场景, 而截止时间适合用于任务需要在指定时间之前完成的场景。两者都用于控制任务的执行时间,但适用场景有所不同。