https://www.fengfengzhidao.com/article/WdlGxI0BEG4v2tWkq3bD#go%E8%AF%AD%E8%A8%80%E7%9A%84context
https://blog.csdn.net/weixin_52690231/article/details/124518402
https://blog.csdn.net/m0_57960197/article/details/132529334 基于源码
学一点,整一点,基本都是综合别人的,弄成我能理解的内容
Context
在 Go 语言中,Context 是一个非常重要的概念,它用于在不同的 goroutine 之间传递请求域的相关数据,并且可以用来控制 goroutine 的生命周期和取消操作。
type Context interface {
Deadline() (deadline time.Time, ok bool) //用于获取 Context 的截止时间,
Done() <-chan struct{} //用于返回一个只读的 channel,用于通知当前 Context 是否已经被取消
Err() error //用于获取 Context 取消的原因
Value(key any) any //用于获取 Context 中保存的键值对数据
}
Context使用
Background、TODO
//context/context.go
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
Background函数会创建一个没有Deadline、没有Value,也不能被Cancel的emptyCtx。通常在一个请求的初始化阶段用Background()创建最顶层的根Context。
ctx := context.Background()
TODO函数和Background一样,也会创建一个emptyCtx,官方文档建议在"本来应该使用外层传递的ctx,而外层却没有传递"的地方使用,就像函数名的含义一样,留下一个TODO。
数值传递 WithValue
func main() {
ctx := context.WithValue(context.Background(), "name", "fengfeng")
GetUser(ctx)
}
func GetUser(ctx context.Context) {
// 获取用户名
fmt.Println(ctx.Value("name"))
}
虽然 context.WithValue 函数允许存储任何类型的值,但是为了保证线程安全,存储的值应该是线程安全的。这意味着值本身不应该是可变的,或者应该使用互斥锁等机制来保护对它的并发访问。如果存储的值是可变的,并且可能会被多个 goroutine 同时修改,那么必须确保对这些值的访问是线程安全的。 (线程安全意味着在多线程环境下,对共享资源的访问不会引发竞态条件,能够保证数据的一致性和完整性。)
var key string="name"
func main() {
ctx, cancel := context.WithCancel(context.Background())
// 附加值
valueCtx:=context.WithValue(ctx,key,"【监控 1】")
go watch(valueCtx)
time.Sleep(10 * time.Second)
fmt.Println("可以了, 通知监控停止")
cancel()
// 为了检测监控过是否停止, 如果没有监控输出, 就表示停止了
time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
// 取出值
fmt.Println(ctx.Value(key),"监控退出, 停止了。..")
return
default:
// 取出值
fmt.Println(ctx.Value(key),"goroutine 监控中。..")
time.Sleep(2 * time.Second)
}
}
}
取消协程 WithCancel
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
}
WithCancel函数会基于传入的parent创建一个可以Cancel的ctx,与cancel函数一起返回。调用cancel函数就会将这个新的ctx Cancel掉,所有基于此ctx创建的子孙Context也会一并被Cancel掉。
func main() {
var wg sync.WaitGroup
ctx := context.Background()
ctx1, cancel = context.WithCancel(ctx)
wg.Add(1)
go func() {
defer wg.Done()
tick := time.NewTicker(300 * time.Millisecond)
for {
select {
case <-ctx1.Done():
fmt.Println(ctx1.Err())
return
case t := <-tick.C:
fmt.Println(t.Nanosecond())
}
}
}()
time.Sleep(time.Second)
cancel()
wg.Wait()
}
截止时间 WithDeadline
除了使用 WithCancel() 方法进行取消操作之外,Context 还可以被用来设置截止时间,以便在超时的情况下取消请求
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判断是超时还是取消
}
}
Context 使用原则
- 不要把 Context 放在结构体中, 要以参数的方式传递
- 以 Context 作为参数的函数方法, 应该把 Context 作为第一个参数, 放在第一位。
- 给一个函数方法传递 Context 的时候, 不要传递 nil, 如果不知道传递什么, 就使用 context.TODO
- Context 的 Value 相关方法应该传递必须的数据, 不要什么数据都使用这个传递
- 保证Context 是线程安全的, 可以放心的在多个 goroutine 中传递
源码
https://blog.csdn.net/m0_57960197/article/details/132529334
cancelCtx
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
cause error // set to non-nil by the first cancel call
}
type canceler interface {
cancel(removeFromParent bool, err, cause error)
Done() <-chan struct{}
}
Context:内置了一个context为父context,可见。
mu:内置了一把锁,用以协调并发场景下的资源获取
实际类型为chan struct{} 用以反映cancelCtx生命周期的通道
children:一个set指向cancelCtx的所有子context
err:记录当前cancelCtx的错误
Done
func (c *cancelCtx) Done() <-chan struct{} {
d := c.done.Load()
if d != nil {
return d.(chan struct{})
}
c.mu.Lock()
defer c.mu.Unlock()
d = c.done.Load()
if d == nil {
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
}
基于atomic包,读取cancelCtx中done chan倘若已存在则直接返回
加锁后,检查chan是否存在,存在返回 双重检查
初始化chan存储到done中返回 懒加载