context 的使用,context是否并发安全(联动问题->多个协程的同时控制)?
Go 的 Context 的数据结构包含 Deadline,Done,Err,Value;它可以控制一组呈树状结构的goroutine,每个goroutine拥有相同的上下文。通过context包,可以非常方便地在请求goroutine之间传递请求数据、取消信号和超时信息。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
基础的context接口只定义了4个方法,下面分别简要说明一下:
- Deadline 方法:可以获取设置的截止时间,返回值 deadline 是截止时间,到了这个时间,Context 会自动发起取消请求,返回值 ok 表示是否设置了截止时间。
- Done 方法:返回一个只读的 channel ,类型为 struct{}。如果这个 chan 可以读取,说明已经发出了取消信号,可以做清理操作,然后退出协程,释放资源。
- Err 方法:返回Context 被取消的原因。
- Value 方法:获取 Context 上绑定的值,是一个键值对,通过 key 来获取对应的值。
最常用的是 Done 方法,在 Context 取消的时候,会关闭这个只读的 Channel,相当于发出了取消信号。
其主要的应用 :
1:上下文控制,2:多个 goroutine 之间的数据交互等,3:超时控制:到某个时间点超时,过多久超时。
Context的使用
我们可以使用 select+channel 来实现了部分协程的终止,但是如果我们想要同时取消多个协程怎么办呢?如果需要定时取消又怎么办呢?
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
ctx, stop := context.WithCancel(context.Background())
wg.Add(1)
go func() {
defer wg.Done()
worker(ctx)
}()
time.Sleep(3*time.Second) //工作3秒
stop() //3秒后发出停止指令 context的停止函数
wg.Wait()
}
func worker(ctx context.Context){
for {
select {
case <- ctx.Done():
fmt.Println("结束")
return
default:
fmt.Println("运行中...")
}
time.Sleep(1*time.Second)
}
}
context是并发安全的,为什么呢?
context包提供两种创建根context的方式:
- context.Backgroud()
- context.TODO()
又提供了四个函数基于父Context衍生,其中使用WithValue函数来衍生context并携带数据,每次调用WithValue函数都会基于当前context衍生一个新的子context,WithValue内部主要就是调用valueCtx类:
valueCtx结构如下:
type valueCtx struct {
Context
key, val interface{}
}
1.2.3.4.
valueCtx继承父Context,这种是采用匿名接口的继承实现方式,key,val用来存储携带的键值对。
通过上面的代码分析,可以看到添加键值对不是在原context结构体上直接添加,而是以此context作为父节点,重新创建一个新的valueCtx子节点,将键值对添加在子节点上,由此形成一条context链。
获取键值过程也是层层向上调用直到最终的根节点,中间要是找到了key就会返回,否会就会找到最终的emptyCtx返回nil。画个图表示一下:
总结:context添加的键值对是链式的,会不断衍生新的context,所以context本身是不可变的,因此是线程安全的。