Golang的关键字defer如何使用
导读:本文共4874字符,通常情况下阅读需要16分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 核心思想在defer出现的地方插入了指令CALL runtime.deferproc,在函数返回的地方插入了CALL runtime.deferreturn。goroutine的控制结构中,有一张表记录defer,调用runtime.deferproc时会将需要defer的表达式记录在表中,而在调用runtime.deferreturn的时候,则会依次从defe... ...
目录
(为您整理了一些要点),点击可以直达。核心思想
在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会添加到链表头,所以感觉像是栈那样先进后出的调用:
源码分析
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。以避免频繁的堆分配和回收
优化
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
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如何使用的详细内容,希望对您有所帮助,信息来源于网络。