本文转载自微信公众号《Golang梦工厂》,作者:AsongGo。转载本文请联系Golang梦工厂公众号。大家好,我是asong。最近在群里看到一篇有趣的八字文章。问题是:上下文携带的值是线程安全的吗?这个问题其实是考察面试官对上下文实现原理的理解。如果不知道context的实现原理,是很难的。这个问题很容易答错,所以在本文中,我们将通过这个问题来重新认识上下文承载值的实现原理。上下文携带的值是线程安全的吗?先说答案吧。上下文本身是线程安全的,所以上下文携带的值也是线程安全的。写一个简单的例子来验证:funcmain(){ctx:=context.WithValue(context.Background(),"asong","test01")gofunc(){for{_=context.WithValue(ctx,"asong","test02")}}()gofunc(){for{_=context.WithValue(ctx,"asong","test03")}}()gofunc(){for{fmt.Println(ctx.Value("asong"))}}()gofunc(){for{fmt.Println(ctx.Value("asong"))}}()time.Sleep(10*time.Second)}程序运行正常,没有任何问题。接下来我们看看context为什么是线程安全的!!!为什么要线程安全?context包提供了两种创建根上下文的方法:context.Backgroud()context.TODO()提供了四个基于父Context派生的函数,其中WithValue函数用于派生上下文并携带数据,分别调用WithValue的函数将根据当前上下文生成一个新的子上下文。WithValue的主要功能是调用valueCtx类:funcWithValue(parentContext,key,valinterface{})Context{ifparent==nil{panic("cannotcreatecontextfromnilparent")}ifkey==nil{panic("nilkey")}if!reflectlite.TypeOf(key).Comparable(){panic("keyisnotcomparable")}return&valueCtx{parent,key,val}}valueCtx的结构如下:typevalueCtxstruct{Contextkey,valinterface{}}valueCtx继承父Context,这是一种使用匿名接口的继承实现方式,使用key和val存储携带的键值对通过上面的代码分析,我们可以看出,添加键值对并不是直接添加到原来的上下文结构中,而是以上下文为父节点重新创建一个新的valueCtx子节点并添加子节点的键值对。这形成了一个上下文链。获取键值的过程也是逐层向上调用,直到最后的根节点。如果中间找到key就返回,否则就找到最后的emptyCtx,返回nil。画图展示一下:image-20220207214507921总结:上下文添加的键值对是一个链,会不断产生新的上下文,所以上下文本身是不可变的,所以是线程安全的。综上所述,本文主要是想带大家回顾一下context的实现原理。面试的时候,面试官喜欢含蓄地提问,所以这就需要我们有扎实的基本功。一不小心,我们就会落入面试官的圈套。处处小心。哦~好了,本文到此结束,我是asong,下期见。已创建读者交流群,欢迎大家加入,共同学习交流。加群方式:关注公众号获取。更多学习资料请到公众号。
