在 Go 语言中,Context 是一个非常重要的概念,它用于在不同的 goroutine 之间传递请求域的相关数据,并且可以用来控制 goroutine 的生命周期和取消操作。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
- Deadline() 方法用于获取 Context 的截止时间,
- Done() 方法用于返回一个只读的 channel,用于通知当前 Context 是否已经被取消,
- Err() 方法用于获取 Context 取消的原因,
- Value() 方法用于获取 Context 中保存的键值对数据。
看一些官方包的源码的时候,很多函数的第一个参数都是 ctx context.Context
这个context是干什么的呢?
Context是干什么的
我们都知道 go的协程写起来是非常轻松的
并且在协程里面还能继续开协程
颇有一种一生二,二生三,三生万物的感觉了
这么多协程,协程与协程之间的通信如何解决呢
这个协程我不想要了,我如何关闭它,我们之前没有讲过
那么context来了,就是解决如何关闭协程这个问题的
除了关闭协程,他还能用于传输数据,做到协程与协程之间的桥梁,所以我们叫他 上下文
数据传递
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.WithValue(context.Background(), "name", "fengfeng")
GetUser(ctx)
}
func GetUser(ctx context.Context) {
// 获取用户名
fmt.Println(ctx.Value("name"))
}
Context 中保存的键值对数据应该是线程安全的,因为它们可能会在多个 goroutine 中同时访问。
取消协程 WithCancel
一个很常见的案例,我有一个获取ip的协程,但是这是一个耗时操作,用户随时可能会取消
如果用户取消了,那么之前那个获取协程的函数就要停止
怎么办呢?
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wait = sync.WaitGroup{}
func main() {
t1 := time.Now()
wait.Add(1)
ctx, cancel := context.WithCancel(context.Background())
go func() {
ip, err := GetIp(ctx)
fmt.Println(ip, err)
}()
wait.Add(1)
go func() {
time.Sleep(2 * time.Second)
cancel()
wait.Done()
}()
wait.Wait()
fmt.Println("执行完成", time.Since(t1))
}
func GetIp(ctx context.Context) (ip string, err error) {
go func() {
select {
case <-ctx.Done():
fmt.Println("取消", ctx.Err().Error())
err = ctx.Err()
wait.Done()
return
}
}()
time.Sleep(3 * time.Second)
ip = "192.168.200.1"
wait.Done()
return
}
截止时间WithDeadline
除了使用 WithCancel() 方法进行取消操作之外,Context 还可以被用来设置截止时间,以便在超时的情况下取消请求
还是上面那个案例
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
go GetIp(ctx)
// 5秒到了,手动结束协程
time.Sleep(5 * time.Second)
//cancel() // 可以手动取消,也可让他自然超时
// 模拟主线程阻塞
time.Sleep(1 * time.Second)
}
func GetIp(ctx context.Context) {
fmt.Println("获取ip中")
// 等待请求完成或者被取消
select {
case <-ctx.Done():
// 请求被取消
fmt.Println("请求超时或被取消", ctx.Err()) // 可以通过err判断是超时还是取消
}
}
超时时间WithTimeout
用法大差不差,也是可以手动取消的
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
go GetIp(ctx)
// 5秒到了,手动结束协程
time.Sleep(5 * time.Second)
//cancel() // 可以手动取消,也可让他自然超时
// 模拟主线程阻塞
time.Sleep(1 * time.Second)
}
func GetIp(ctx context.Context) {
fmt.Println("获取ip中")
// 等待请求完成或者被取消
select {
case <-ctx.Done():
// 请求被取消
fmt.Println("请求超时或被取消", ctx.Err()) // 可以通过err判断是超时还是取消
}
}
参考文档
context讲解 https://zhuanlan.zhihu.com/p/628201027
context学习笔记 https://blog.csdn.net/QcloudCommunity/article/details/126476476