百摩网
当前位置: 首页 生活百科

go源码分析(语言源码阅读有感)

时间:2023-07-07 作者: 小编 阅读量: 4 栏目名: 生活百科

以下文章来源于Golang技术分享,作者机器铃砍菜刀Golang技术分享专注于Go语言的知识分享小菜刀读Go源码时,发现一个高频注释语句“XXXmustnotbecopiedAfterfirstuse“。例如sync包下的Pool、Cond、WaitGroup、Mutex、Map和atomoic.Vaule、strings.Builder等,都有该句注释。为什么注释文档中要强调nocopy?安全!

以下文章来源于Golang技术分享 ,作者机器铃砍菜刀

Golang技术分享

专注于Go语言的知识分享

小菜刀读Go源码时,发现一个高频注释语句“XXX must not be copied After first use“。例如sync包下的Pool、Cond、WaitGroup、Mutex、Map和atomoic.Vaule、strings.Builder等,都有该句注释。

为什么注释文档中要强调no copy?

安全!

如果结构体对象包含指针字段,当该对象被拷贝时,会使得两个对象中的指针字段变得不再安全。

typeSstruct{f1intf2*s}typesstruct{namestring}funcmain(){mOld:=S{f1:0,f2:&s{name:"mike"},}mNew:=mOld//拷贝mNew.f1=1mNew.f2.name="jane"fmt.Println(mOld.f1,mOld.f2)//输出:0&{jane}}

如上,结构体对象S中存在两个field,分别是f1和f2,其中f2是指向s类型的指针。当mNew复制了mOld之后,mNew对两个字段进行了改变,可以看到f1字段的更改,不会对mOld造成影响。但是,nNew中f2字段的修改也会把mOld中的f2字段修改掉,这引发了安全问题。

Go是如何保证no copy的?

1. runtime checking

  • strings.Builder中copy检查

funcmain(){varastrings.Buildera.Write([]byte("a"))b:=ab.Write([]byte("b"))}//运行报错:panic: strings: illegal use of non-zero Builder copied by value

报错信息,来源于strings.Builder的copycheck。

typeBuilderstruct{addr*Builder//ofreceiver,todetectcopiesbyvaluebuf[]byte}func(b*Builder)Write(p[]byte)(int,error){b.copyCheck()b.buf=append(b.buf,p...)returnlen(p),nil}func(b*Builder)copyCheck(){ifb.addr==nil{b.addr=(*Builder)(noescape(unsafe.Pointer(b)))}elseifb.addr!=b{panic("strings:illegaluseofnon-zeroBuildercopiedbyvalue")}}

在Builder中,addr是一个指向自身的指针。当对上文中的a复制给b时,a和b本身是不同的对象。因此,b.addr实际还是指向a的指针,这就会触发条件b.addr!=b,造成panic。

  • sync.Cond中copy检查

在源码中,拥有copy检查机制的还有sync.Cond。

typeCondstruct{noCopynoCopyLLockernotifynotifyListcheckercopyChecker}func(c*Cond)Wait(){c.checker.check()...}typecopyCheckeruintptrfunc(c*copyChecker)check(){ifuintptr(*c)!=uintptr(unsafe.Pointer(c))&&!atomic.CompareAndSwapUintptr((*uintptr)(c),0,uintptr(unsafe.Pointer(c)))&&uintptr(*c)!=uintptr(unsafe.Pointer(c)){panic("sync.Condiscopied")}}

这里的check函数初看不易明白。因此,定义一个相似的结构体对象,来探究这里的check函数究竟是如何做copy检查的。

typecondstruct{checkercopyChecker}typecopyCheckeruintptrfunc(c*copyChecker)check(){fmt.Printf("Before:c:%12v,*c:v,uintptr(*c):v,uintptr(unsafe.Pointer(c)):v\n",c,*c,uintptr(*c),uintptr(unsafe.Pointer(c)))swapped:=atomic.CompareAndSwapUintptr((*uintptr)(c),0,uintptr(unsafe.Pointer(c)))fmt.Printf("After:c:v,*c:v,uintptr(*c):v,uintptr(unsafe.Pointer(c)):v,swapped:v\n",c,*c,uintptr(*c),uintptr(unsafe.Pointer(c)),swapped)}funcmain(){varaconda.checker.check()b:=ab.checker.check()}//输出Before:c:0xc0000b4008,*c:0,uintptr(*c):0,uintptr(unsafe.Pointer(c)):824634458120After:c:0xc0000b4008,*c:824634458120,uintptr(*c):824634458120,uintptr(unsafe.Pointer(c)):824634458120,swapped:trueBefore:c:0xc0000b4040,*c:824634458120,uintptr(*c):824634458120,uintptr(unsafe.Pointer(c)):824634458176After:c:0xc0000b4040,*c:824634458120,uintptr(*c):824634458120,uintptr(unsafe.Pointer(c)):824634458176,swapped:false

这下,sync.Cond的copy检查就很清晰了。当a被b copy之后,uintptr(*c)和uintptr(unsafe.Pointer(c))的值是不同的,通过uint对象的原子比较方法CompareAndSwapUintptr将返回false,它证明了对象a被copy过,从而调用panic保护sync.Cond不被复制。

2. go vet checking

上述两个例子都是在程序编译后,runtime检查的。但是,正如文中开篇所述,sync包下的其他的对象如Pool、WaitGroup、Mutex、Map等,它们其实也需要copy检查机制,但是在源码中,却没有提供运行时检查。那该如何保证我们的代码中这些对象在使用中未被copy,从而避免潜在的安全问题呢?

Go在源代码src/sync/cond.go中的一段注释给了我们答案。

//noCopymaybeembeddedintostructswhichmustnotbecopied//afterthefirstuse.////Seehttps://golang.org/issues/8005#issuecomment-190753527//fordetails.typenoCopystruct{}//Lockisano-opusedby-copylockscheckerfrom`govet`.func(*noCopy)Lock(){}func(*noCopy)Unlock(){}

很明显,runtime时的copy检查虽然很重要,但是,该操作会影响程序的执行性能。Go官方目前只提供了strings.Builder和sync.Cond的runtime拷贝检查机制,对于其他需要nocopy对象类型来说,使用go vet工具来做静态编译检查。

具体实施来说,就是该对象,或对象中存在filed,它拥有Lock()和Unlock()方法,即实现sync.Locker接口。之后,可以通过go vet功能,来检查代码中该对象是否有被copy。

例如sync.Pool和sync.WaitGroup就内嵌了noCopy属性,sync.Mutex实现了sync.Locker接口,sync.Map内嵌了sync.Mutex。

  • 静态检查

// wg.gopackage mainimport "sync"func main() {var sm sync.Mutexsm.Lock()sm.Unlock()sm2 := smsm2.Lock()}

如上,sm在first use后,被copy给sm2。注意:该代码运行时,不会报错,但是却存在安全隐患。

$govetwg.go#command-line-arguments./wg.go:9:9:assignmentcopieslockvaluetosm2:sync.Mutex

通过以上命令,即可检查出sync.Mutex有被copy。因此,举一反三,如果在我们自己的项目开发中,定义某对象不能被copy,那么就可以参考Go源码中,嵌入noCopy结构体,最终通过go vet进行copy检查。

typenoCopystruct{}func(*noCopy)Lock(){}func(*noCopy)Unlock(){}typeMyTypestruct{noCopynoCopy...}

更多关于Go关于no copy的讨论请参考官方Github issue,地址:https://github.com/golang/go/issues/8005

    推荐阅读
  • 健身说到的rm是什么(RM值是什么意思每个健身者都要知道的原理)

    RM是指力量训练中最高的重复次数。不同举次范围的RM负荷对肌肉的刺激作用各不相同,因此,在锻炼实践中必须严格认证所需采用次数的RM负荷。

  • 唯一一部希望反派能赢的电影(有没有不完美或者说恶势力获得胜利为结局的电影)

    唯一一部希望反派能赢的电影一、根据大多数人的人生经验,我发现“我很迷茫”的真实意思往往是:又不想好好努力,又想继续混吃混喝,可眼看着就要撑不下去了,该怎么办呐...他俩挺般配的!开车又快又稳,车上放的音乐也很有品味。她说有只老鼠窜进来不知躲哪儿了,她害怕,就假装自己是只猫。

  • 洛天依是什么技术 洛天依是什么技术制作的

    2、洛天依的声源是国内配音演员山新,形象由MOTH投稿设计,经ideolo改编整合后完成,2012年7月12日,洛天依作为中文虚拟歌手正式出道。

  • 胸贴可以使用几次(胸贴能重复使用几次)

    胸贴可以使用几次胸贴的使用次数与胶水质量、穿戴时长有关,具体次数根据这方面决定。另外在夏天时,乳头分泌物增多,而且还有汗液渗出,可能会造成乳头发炎。另一种是使用胶水贴到乳房上的胸贴,长期使用可能会令皮肤发痒发红,如果是过敏性肌肤可能更容易过敏,因此,建议偶尔使用乳贴,不能代替乳罩长期使用。

  • 偷笑门是什么梗

    然而,孙悦、杜锋和朱芳雨三人在领奖台上的诡异笑容却与以上的情感格格不入。球迷不干了、篮协领导不干了、国家体育总局的领导也不干了。中国男篮队员在领奖台上捂嘴偷笑的视频在网上流传,偷笑门成为网络热门话题。总之,谁都不想让他们这样在球迷的伤口上“撒盐”。

  • 怎样训教牡丹鹦鹉(如何喂养刚出生的牡丹鹦鹉)

    很多第一次当爸爸妈妈的牡丹鹦鹉照顾不好小鸟,那么这个时候就需要,我们鸟主人担负起奶爸的责任了。手养牡丹鹦鹉必备用品一、保温箱孵化出的雏鸟牡丹鹦鹉发育过程分为五个阶段,绒羽期、针羽期、羽片前期、羽片后期、齐羽期,前面四个阶段都是需要放在保温箱的,尤其是前期鹦鹉羽毛还没有齐时。每次喂食温度要控制在37~42的温度最好,过高的温度可能造成雏鸟嗉囊的烫伤,过低的温度可能造成雏鸟消化变慢或拒食的情形。

  • 二手房能提取公积金吗(二手房是否可以提取公积金)

    二手房能提取公积金吗购买二手房后可以凭提取人的身份证、《郑州住房公积金提取申请表》、有效期在五年内《房屋所有权证》等相关证件去住房公积金管理中心进行提取。以郑州住房公积金为例,根据郑州住房公积金中心下发的《提取住房公积金明白卡》第一条购买商品房二手房提取住房公积金。提取金额不足的,购房人的父母、配偶、子女、共有人可申请提取本人的住房公积金。提取金额总计不能超过职工已支付购房款。

  • 抖音天花板是什么意思 最近很火的天花板是什么意思

    演示机型:Iphone13&&华为P50&&小米11系统版本:iOS15&&HarmonyOS2&&MIUI12.5APP版本:抖音v17.9.0抖音天花板是指能力达到了顶峰,属于最高水平,几乎不能被超越。比如一个歌曲视频很棒则叫歌曲天花板,或视频流量相当大则叫流量天花板。因天花板是一座建筑物室内顶部表面的地方,大多都有很高的高度,因此被饭圈用作指某位明星爱豆是唱跳、业务能力顶尖的意思。

  • 手机保存不了图片是怎么回事 小米手机保存不了图片是怎么回事

    如果是这样,把它从黑名单中删除。建议用户备份好手机的通讯录,短信等数据,然后进入设置,将手机恢复出厂设置一下。手机在恢复出厂后会恢复到初始的状态,清除手机除系统之外的所有数据,然后用户在尝试保存照片等,通常即可恢复正常;

  • usb换成typec(我是如何将旧设备的)

    我是如何将旧设备的安全提示:本文涉及电子产品的充电接口及电路改装,有可能产生安全隐患,改造过程仅供参考背景我家里的许多旧设备,都是Micro-USB充电口,即便现在去买一些电子产品(如充电台灯),许多设备依旧脱离时代的。