Golang的关键字defer如何使用(defer,golang,开发技术)

时间:2024-05-06 00:07:17 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

核心思想

在defer出现的地方插入了指令CALL runtime.deferproc,在函数返回的地方插入了CALL runtime.deferreturn。goroutine的控制结构中,有一张表记录defer,调用runtime.deferproc时会将需要defer的表达式记录在表中,而在调用runtime.deferreturn的时候,则会依次从defer表中“出栈”并执行

如果有多个defer,调用顺序类似栈,越后面的defer表达式越先被调用

defer链

defer信息会注册到链表,当前执行的 goroutine 持有这个链表的头指针,每个 goroutine 都有一个对应的结构体struct G,其中有一个字段指向这个defer链表头

typegstruct{ //Stackparameters. //stackdescribestheactualstackmemory:[stack.lo,stack.hi). //stackguard0isthestackpointercomparedintheGostackgrowthprologue. //Itisstack.lo+StackGuardnormally,butcanbeStackPreempttotriggerapreemption. //stackguard1isthestackpointercomparedintheCstackgrowthprologue. //Itisstack.lo+StackGuardong0andgsignalstacks. //Itis~0onothergoroutinestacks,totriggeracalltomorestackc(andcrash). stackstack//offsetknowntoruntime/cgo stackguard0uintptr//offsetknowntoliblink stackguard1uintptr//offsetknowntoliblink _panic*_panic//innermostpanic-offsetknowntoliblink//_defer这个字段指向defer链表头 _defer*_defer//innermostdefer...}

新注册的defer会添加到链表头,所以感觉像是栈那样先进后出的调用:

Golang的关键字defer如何使用

源码分析

deferproc一共有两个参数,第一个是参数和返回值的大小,第二个是指向funcval的指针

//Createanewdeferredfunctionfnwithsizbytesofarguments.//Thecompilerturnsadeferstatementintoacalltothis.//go:nosplitfuncdeferproc(sizint32,fn*funcval){//argumentsoffnfollowfn//获取当前goroutine gp:=getg() ifgp.m.curg!=gp{ //gocodeonthesystemstackcan'tdefer throw("deferonsystemstack") } //theargumentsoffnareinaperilousstate.Thestackmap //fordeferprocdoesnotdescribethem.Sowecan'tletgarbage //collectionorstackcopyingtriggeruntilwe'vecopiedthemout //tosomewheresafe.Thememmovebelowdoesthat. //Untilthecopycompletes,wecanonlycallnosplitroutines.//获取调用者指针 sp:=getcallersp()//通过偏移获得参数 argp:=uintptr(unsafe.Pointer(&fn))+unsafe.Sizeof(fn) callerpc:=getcallerpc()//创建defer结构体 d:=newdefer(siz) ifd._panic!=nil{ throw("deferproc:d.panic!=nilafternewdefer") }//初始化 d.link=gp._defer gp._defer=d d.fn=fn d.pc=callerpc d.sp=sp switchsiz{ case0: //Donothing. casesys.PtrSize: *(*uintptr)(deferArgs(d))=*(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d),unsafe.Pointer(argp),uintptr(siz)) } //deferprocreturns0normally. //adeferredfuncthatstopsapanic //makesthedeferprocreturn1. //thecodethecompilergeneratesalways //checksthereturnvalueandjumpstothe //endofthefunctionifdeferprocreturns!=0. return0() //Nocodecangohere-theCreturnregisterhas //beensetandmustnotbeclobbered.}
//以下是_defer结构体//A_deferholdsanentryonthelistofdeferredcalls.//Ifyouaddafieldhere,addcodetoclearitinfreedeferanddeferProcStack//Thisstructmustmatchthecodeincmd/compile/internal/gc/reflect.go:deferstruct//andcmd/compile/internal/gc/ssa.go:(*state).call.//Somedeferswillbeallocatedonthestackandsomeontheheap.//Alldefersarelogicallypartofthestack,sowritebarriersto//initializethemarenotrequired.Alldefersmustbemanuallyscanned,//andforheapdefers,marked.type_deferstruct{//siz记录defer的参数和返回值共占多少字节//会直接分配在_defer后面,在注册时保存参数,在执行完成时拷贝到调用者参数和返回值空间 sizint32//includesbothargumentsandresults //started标记是否已经执行startedbool//heapgo1.13优化,标识是否为堆分配 heapbool //openDeferindicatesthatthis_deferisforaframewithopen-coded //defers.Wehaveonlyonedeferrecordfortheentireframe(whichmay //currentlyhave0,1,ormoredefersactive).//openDefer是否是opendefer,通过这些信息可以找到未注册到链表的defer函数 openDeferbool//sp记录调用者栈指针,可以通过它判断自己注册的defer是否已经执行完了 spuintptr//spattimeofdefer//pcdeferproc的返回地址 pcuintptr//pcattimeofdefer//fn要注册的funcval fn*funcval//canbenilforopen-codeddefers//_panic指向当前的panic,表示这个defer是由这个panic触发的 _panic*_panic//panicthatisrunningdefer//link链到前一个注册的defer结构体 link*_defer //IfopenDeferistrue,thefieldsbelowrecordvaluesaboutthestack //frameandassociatedfunctionthathastheopen-codeddefer(s).sp //abovewillbethespfortheframe,andpcwillbeaddressofthe //deferreturncallinthefunction.//通过这些信息可以找到未注册到链表的defer函数 fdunsafe.Pointer//funcdataforthefunctionassociatedwiththeframe varpuintptr//valueofvarpforthestackframe //framepcisthecurrentpcassociatedwiththestackframe.Together, //withspabove(whichisthespassociatedwiththestackframe), //framepc/spcanbeusedaspc/sppairtocontinueastacktracevia //gentraceback(). framepcuintptr}

defer将参数注册的时候拷贝到堆上,执行时再(将参数和返回值)拷贝回栈上

go会分配不同规格的_defer pool,执行时从空闲_defer中取一个出来用,没有合适的再进行堆分配。用完以后再放回空闲_defer pool。以避免频繁的堆分配和回收

Golang的关键字defer如何使用

优化

go1.12中defer存在的问题:

  • defer信息主要存储在堆上,要在堆和栈上来回拷贝返回值和参数很慢

  • defer结构体通过链表链起来,而链表的操作也很慢

go1.13中defer的优化:

  • 减少了defer信息的堆分配。再通过deferprocStack将整个defer注册到defer链表中

  • 将一般情况的defer信息存储在函数栈帧的局部变量区域

  • 显示循环或者是隐式循环的defer还是需要用到go1.12中defer信息的堆分配

  • 官方给出的性能提升是30%

go1.14中defer的优化:

  • 在编译阶段插入代码,把defer函数的执行逻辑展开在所属函数内,避免创建defer结构体,而且不需要注册到defer链表。称为 open coded defer

  • 与1.13一样不适用于循环中的defer

  • 性能几乎提升了一个数量级

  • open coded defer 中发生panic 或 调用runtime.Goexit(),后面未注册到的defer函数无法执行到,需要栈扫描。defer结构体中就多添加了一些字段,借助这些字段可以找到未注册到链表中的defer函数

结果就是defer变快了,但是panic变慢了

defer添加了局部变量去判断是否需要执行,需要执行的话就将标识df对应的位上或一下,如果是有条件的defer,需要根据具体条件去或df

Golang的关键字defer如何使用

deferprocStack

//deferprocStackqueuesanewdeferredfunctionwithadeferrecordonthestack.//Thedeferrecordmusthaveitssizandfnfieldsinitialized.//Allotherfieldscancontainjunk.//Thedeferrecordmustbeimmediatelyfollowedinmemoryby//theargumentsofthedefer.//Nosplitbecausetheargumentsonthestackwon'tbescanned//untilthedeferrecordissplicedintothegp._deferlist.//go:nosplitfuncdeferprocStack(d*_defer){//获得当前goroutine gp:=getg() ifgp.m.curg!=gp{ //gocodeonthesystemstackcan'tdefer throw("deferonsystemstack") } //sizandfnarealreadyset. //TheotherfieldsarejunkonentrytodeferprocStackand //areinitializedhere.//初始化_defer信息 d.started=false d.heap=false d.openDefer=false d.sp=getcallersp() d.pc=getcallerpc() d.framepc=0 d.varp=0 //Thelinesbelowimplement: //d.panic=nil //d.fd=nil //d.link=gp._defer //gp._defer=d //Butwithoutwritebarriers.Thefirstthreearewritesto //thestacksotheydon'tneedawritebarrier,andfurthermore //aretouninitializedmemory,sotheymustnotuseawritebarrier. //Thefourthwritedoesnotrequireawritebarrierbecausewe //explicitlymarkallthedeferstructures,sowedon'tneedto //keeptrackofpointerstothemwithawritebarrier. *(*uintptr)(unsafe.Pointer(&d._panic))=0 *(*uintptr)(unsafe.Pointer(&d.fd))=0 *(*uintptr)(unsafe.Pointer(&d.link))=uintptr(unsafe.Pointer(gp._defer)) *(*uintptr)(unsafe.Pointer(&gp._defer))=uintptr(unsafe.Pointer(d)) return0() //Nocodecangohere-theCreturnregisterhas //beensetandmustnotbeclobbered.}
 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:Golang的关键字defer如何使用的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:Redis怎么远程连接Redis客户端下一篇:

8 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18