0%

go语言基础6--context相关

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。画个图表示一下:

img

总结:context添加的键值对是链式的,会不断衍生新的context,所以context本身是不可变的,因此是线程安全的。

参考文章:Go语言,并发控制神器之Context - 掘金 (juejin.cn)

参考文章: 面试官:Context携带数据是线程安全的吗?-51CTO.COM

-------------本文结束感谢您的阅读-------------
打赏一瓶矿泉水