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),"监控退出, 停止了。..")returndefault:// 取出值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.WaitGroupctx := 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())returncase 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 {Contextmu sync.Mutex // protects following fieldsdone atomic.Value // of chan struct{}, created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr error // set to non-nil by the first cancel callcause 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中返回 懒加载