-
Go中常用的并发模型之一;(一共有三种:通过channel通知实现并发控制;通过sync包中的WaitGroup实现并发控制;Context上下文,实现并发控制)
-
context对goroutine进行跟踪,从而达到控制他们的目的
-
context接口不需要实现,其内置了两个方法帮我们实现了:
var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
Background
,主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。和todo本质都是emptyCtx,不可取消;不携带任何的值 -
Context的接口主要有四个;
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
Deadline
方法:是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。
Done
方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。
Err
方法返回取消的错误原因,因为什么Context被取消。
Value
方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的。 -
子Context的继承衍生主要有
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context
他们都传递一个父Context作为参数;返回子Context
WithCancel
函数,返回子Context,以及一个取消函数用来取消Context。
WithDeadline
函数,和WithCancel
差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。WithTimeout
和WithDeadline
基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思。WithValue
函数和取消Context
无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到
func f2(ctx context.Context) {
LOOP:
for {
fmt.Println("worder 2")
time.Sleep(time.Millisecond * 200)
select {
case <-ctx.Done(): // 等待上级通知
break LOOP
default:
}
}
}
func f(ctx context.Context) {
go f2(ctx)
LOOP:
for {
fmt.Println("worder 1")
time.Sleep(time.Millisecond * 200)
select {
case <-ctx.Done(): // 等待上级通知
break LOOP
default:
}
}
defer wg.Done()
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go f(ctx)
time.Sleep(time.Second * 2)
cancel() //通知子goroutine结束
wg.Wait()
}
//withDeadline()
func main(){
d := time.Now().Add(time.Second * 1)
ctx2, cancel2 := context.WithDeadline(context.Background(), d)
//尽管ctx2会过期,但在任何情况下调用它的cancel2函数都是很好的实践。
//如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。
defer cancel2()
select {
case <-ctx2.Done():
fmt.Println(ctx2.Err())
case <-time.After(time.Millisecond * 2000):
fmt.Println("working...")
}
}
//WithTimeout()通常用于数据库或者网络连接的超时控制。
func main(){
ctx3, cancel3 := context.WithTimeout(context.Background(),time.Millisecond*50) // 设置一个50毫秒的超时
wg.Add(1)
go connectDB(ctx3)
time.Sleep(time.Second * 2)
cancel3()
wg.Wait()
}
type TraceCode string
func withvalue(ctxP context.Context) {
key := TraceCode("TRACE_CODE")
traceCode, ok := ctxP.Value(key).(string) // 在子goroutine中获取trace code .()类型断言
if !ok {
fmt.Println("invalid trace code")
return
}
LOOP:
for {
fmt.Printf("worker, trace code:%s\n", traceCode)
time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
select {
case <-ctxP.Done(): // 50毫秒后自动调用
break LOOP
default:
}
}
wg.Done()
}
//WithValue()
func main(){
ctxP, cancel := context.WithTimeout(context.Background(), time.Millisecond*50) // 设置一个50毫秒的超时
// 在系统的入口中设置trace code传递给后续启动的goroutine实现日志数据聚合
ctxP = context.WithValue(ctxP, TraceCode("TRACE_CODE"), "123123")
wg.Add(1)
go withvalue(ctxP)
time.Sleep(time.Second * 5)
cancel()
wg.Wait()
}