您好,登錄后才能下訂單哦!
這篇文章主要介紹“GO語言中defer實現(xiàn)原理是什么”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強(qiáng),希望這篇“GO語言中defer實現(xiàn)原理是什么”文章能幫助大家解決問題。
咱們一起來看看 defer
是個啥
是 GO 中的一個關(guān)鍵字
這個關(guān)鍵字,我們一般用在釋放資源,在 return
前會調(diào)用他
如果程序中有多個 defer
,defer 的調(diào)用順序是按照類似棧的方式,后進(jìn)先出 LIFO
的 ,這里順便寫一下
棧
遵循后進(jìn)先出原則
后進(jìn)入棧的,先出棧
先進(jìn)入棧的,后出棧
隊列
遵循先進(jìn)先出 , 我們就可以想象一個單向的管道,從左邊進(jìn),右邊出
先進(jìn)來,先出去
后進(jìn)來,后出去,不準(zhǔn)插隊
咱們先拋出一個結(jié)論,先心里有點底:
代碼中聲明 defer
的位置,編譯的時候會插入一個函數(shù)叫做 deferproc
,在該defer
所在的函數(shù)前插入一個返回的函數(shù),不是return
哦,是deferreturn
具體的 defer
的實現(xiàn)原理是咋樣的,我們還是一樣的,來看看 defer
的底層數(shù)據(jù)結(jié)構(gòu)是啥樣的 ,
在 src/runtime/runtime2.go
的 type _defer struct {
結(jié)構(gòu)
// A _defer holds an entry on the list of deferred calls. // If you add a field here, add code to clear it in freedefer and deferProcStack // This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct // and cmd/compile/internal/gc/ssa.go:(*state).call. // Some defers will be allocated on the stack and some on the heap. // All defers are logically part of the stack, so write barriers to // initialize them are not required. All defers must be manually scanned, // and for heap defers, marked. type _defer struct { siz int32 // includes both arguments and results started bool heap bool // openDefer indicates that this _defer is for a frame with open-coded // defers. We have only one defer record for the entire frame (which may // currently have 0, 1, or more defers active). openDefer bool sp uintptr // sp at time of defer pc uintptr // pc at time of defer fn *funcval // can be nil for open-coded defers _panic *_panic // panic that is running defer link *_defer // If openDefer is true, the fields below record values about the stack // frame and associated function that has the open-coded defer(s). sp // above will be the sp for the frame, and pc will be address of the // deferreturn call in the function. fd unsafe.Pointer // funcdata for the function associated with the frame varp uintptr // value of varp for the stack frame // framepc is the current pc associated with the stack frame. Together, // with sp above (which is the sp associated with the stack frame), // framepc/sp can be used as pc/sp pair to continue a stack trace via // gentraceback(). framepc uintptr }
_defer
持有延遲調(diào)用列表中的一個條目 ,我們來看看上述數(shù)據(jù)結(jié)構(gòu)的參數(shù)都是啥意思
tag | 說明 |
---|---|
siz | defer函數(shù)的參數(shù)和結(jié)果的內(nèi)存大小 |
fn | 需要被延遲執(zhí)行的函數(shù) |
_panic | defer 的 panic 結(jié)構(gòu)體 |
link | 同一個協(xié)程里面的defer 延遲函數(shù),會通過該指針連接在一起 |
heap | 是否分配在堆上面 |
openDefer | 是否經(jīng)過開放編碼優(yōu)化 |
sp | 棧指針(一般會對應(yīng)到匯編) |
pc | 程序計數(shù)器 |
defer 關(guān)鍵字后面必須是跟函數(shù),這一點咱們要記住哦
通過上述參數(shù)的描述,我們可以知道,defer
的數(shù)據(jù)結(jié)構(gòu)和函數(shù)類似,也是有如下三個參數(shù):
棧指針 SP
程序計數(shù)器 PC
函數(shù)的地址
可是我們是不是也發(fā)現(xiàn)了,成員里面還有一個link
,同一個協(xié)程里面的defer 延遲函數(shù),會通過該指針連接在一起
這個link
指針,是指向的一個defer
單鏈表的頭,每次咱們聲明一個defer
的時候,就會將該defer
的數(shù)據(jù)插入到這個單鏈表頭部的位置,
那么,執(zhí)行defer
的時候,我們是不是就能猜到defer
是咋取得了不?
前面有說到defer
是后進(jìn)先出的,這里當(dāng)然也是遵循這個道理,取defer
進(jìn)行執(zhí)行的時候,是從單鏈表的頭開始去取的。
咱們來畫個圖形象一點
在協(xié)程A中聲明2個defer
,先聲明 defer test1()
再聲明 defer test2()
可以看出后聲明的defer
會插入到單鏈表的頭,先聲明的defer
被排到后面去了
咱們?nèi)〉臅r候也是一直取頭下來執(zhí)行,直到單鏈表為空。
咱一起來看看defer 的具體實現(xiàn)
源碼文件在 src/runtime/panic.go
中,查看 函數(shù) deferproc
// Create a new deferred function fn with siz bytes of arguments. // The compiler turns a defer statement into a call to this. //go:nosplit func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn gp := getg() if gp.m.curg != gp { // go code on the system stack can't defer throw("defer on system stack") } // the arguments of fn are in a perilous state. The stack map // for deferproc does not describe them. So we can't let garbage // collection or stack copying trigger until we've copied them out // to somewhere safe. The memmove below does that. // Until the copy completes, we can only call nosplit routines. sp := getcallersp() argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) callerpc := getcallerpc() d := newdefer(siz) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } d.link = gp._defer gp._defer = d d.fn = fn d.pc = callerpc d.sp = sp switch siz { case 0: // Do nothing. case sys.PtrSize: *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) } // deferproc returns 0 normally. // a deferred func that stops a panic // makes the deferproc return 1. // the code the compiler generates always // checks the return value and jumps to the // end of the function if deferproc returns != 0. return0() // No code can go here - the C return register has // been set and must not be clobbered. }
deferproc 的作用是
創(chuàng)建一個新的遞延函數(shù) fn
,參數(shù)為 siz 字節(jié),編譯器將一個延遲語句轉(zhuǎn)換為對this
的調(diào)用
getcallersp()
:
得到deferproc
之前的rsp
寄存器的值,實現(xiàn)的方式所有平臺都是一樣的
//go:noescape func getcallersp() uintptr // implemented as an intrinsic on all platforms
callerpc := getcallerpc()
:
此處得到 rsp
之后,存儲在 callerpc
中 , 此處是為了調(diào)用 deferproc
的下一條指令
d := newdefer(siz)
:
d := newdefer(siz)
新建一個defer
的結(jié)構(gòu),后續(xù)的代碼是在給defer
這個結(jié)構(gòu)的成員賦值
咱看看 deferproc 的大體流程
獲取 deferproc
之前的rsp寄存器的值
使用newdefer
分配一個 _defer 結(jié)構(gòu)體對象,并且將他放到當(dāng)前的 _defer
鏈表的頭
初始化_defer 的相關(guān)成員參數(shù)
return0
來我們看看 newdefer的源碼
源碼文件在 src/runtime/panic.go
中,查看函數(shù)newdefer
// Allocate a Defer, usually using per-P pool. // Each defer must be released with freedefer. The defer is not // added to any defer chain yet. // // This must not grow the stack because there may be a frame without // stack map information when this is called. // //go:nosplit func newdefer(siz int32) *_defer { var d *_defer sc := deferclass(uintptr(siz)) gp := getg() if sc < uintptr(len(p{}.deferpool)) { pp := gp.m.p.ptr() if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil { // Take the slow path on the system stack so // we don't grow newdefer's stack. systemstack(func() { lock(&sched.deferlock) for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil { d := sched.deferpool[sc] sched.deferpool[sc] = d.link d.link = nil pp.deferpool[sc] = append(pp.deferpool[sc], d) } unlock(&sched.deferlock) }) } if n := len(pp.deferpool[sc]); n > 0 { d = pp.deferpool[sc][n-1] pp.deferpool[sc][n-1] = nil pp.deferpool[sc] = pp.deferpool[sc][:n-1] } } if d == nil { // Allocate new defer+args. systemstack(func() { total := roundupsize(totaldefersize(uintptr(siz))) d = (*_defer)(mallocgc(total, deferType, true)) }) } d.siz = siz d.heap = true return d }
newderfer
的作用:
通常使用per-P池,分配一個Defer
每個defer
可以自由的釋放。當(dāng)前defer
也不會加入任何一個 defer
鏈條中
getg()
:
獲取當(dāng)前協(xié)程的結(jié)構(gòu)體指針
// getg returns the pointer to the current g. // The compiler rewrites calls to this function into instructions // that fetch the g directly (from TLS or from the dedicated register). func getg() *g
pp := gp.m.p.ptr()
:
拿到當(dāng)前工作線程里面的 P
然后拿到 從全局的對象池子中拿一部分對象給到P的池子里面
for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil { d := sched.deferpool[sc] sched.deferpool[sc] = d.link d.link = nil pp.deferpool[sc] = append(pp.deferpool[sc], d) }
點進(jìn)去看池子的數(shù)據(jù)結(jié)構(gòu),其實里面的成員也就是 咱們之前說到的 _defer
指針
其中 sched.deferpool[sc]
是全局的池子,pp.deferpool[sc]
是本地的池子
mallocgc分配空間
上述操作若 d 沒有拿到值,那么就直接使用 mallocgc
重新分配,且設(shè)置好 對應(yīng)的成員 siz
和 heap
if d == nil { // Allocate new defer+args. systemstack(func() { total := roundupsize(totaldefersize(uintptr(siz))) d = (*_defer)(mallocgc(total, deferType, true)) }) } d.siz = siz d.heap = true
mallocgc
具體實現(xiàn)在 src/runtime/malloc.go
中,若感興趣的話,可以深入看看這一塊,今天咱們不重點說這個函數(shù)
// Allocate an object of size bytes. // Small objects are allocated from the per-P cache's free lists. // Large objects (> 32 kB) are allocated straight from the heap. func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {}
最后再來看看return0
最后再來看看 deferproc
函數(shù)中的 結(jié)果返回return0()
// return0 is a stub used to return 0 from deferproc. // It is called at the very end of deferproc to signal // the calling Go function that it should not jump // to deferreturn. // in asm_*.s func return0()
return0
是用于從deferproc
返回0
的存根
它在deferproc
函數(shù)的最后被調(diào)用,用來通知調(diào)用Go
的函數(shù)它不應(yīng)該跳轉(zhuǎn)到deferreturn
。
在正常情況下 return0
正常返回 0
可是異常情況下 return0
函數(shù)會返回 1,此時GO 就會跳轉(zhuǎn)到執(zhí)行 deferreturn
簡單說下 deferreturn
deferreturn
的作用就是情況defer
里面的鏈表,歸還相應(yīng)的緩沖區(qū),或者把對應(yīng)的空間讓GC
回收調(diào)
上面分析了GO 中defer
的實現(xiàn)原理之后,咱們現(xiàn)在來了解一下 GO 中應(yīng)用defer
是需要遵守 3 個規(guī)則的,咱們來列一下:
defer
后面跟的函數(shù),叫延遲函數(shù),函數(shù)中的參數(shù)在defer
語句聲明的時候,就已經(jīng)確定下來了
延遲函數(shù)的執(zhí)行時按照后進(jìn)先出來的,文章前面也多次說到過,這個印象應(yīng)該很深刻吧,先出現(xiàn)的defer
后執(zhí)行,后出現(xiàn)的defer
先執(zhí)行
延遲函數(shù)可能會影響到整個函數(shù)的返回值
咱們還是要來解釋一下的,上面第 2 點,應(yīng)該都好理解,上面的圖也表明了 執(zhí)行順序
延遲函數(shù)中的參數(shù)在defer
語句聲明的時候,就已經(jīng)確定下來了
func main() { num := 1 defer fmt.Println(num) num++ return }
別猜了,運(yùn)行結(jié)果是 1,小伙伴們可以將代碼拷貝下來,自己運(yùn)行一波
延遲函數(shù)可能會影響到整個函數(shù)的返回值
func test3() (res int) { defer func() { res++ }() return 1 } func main() { fmt.Println(test3()) return }
上述代碼,我們在 test3
函數(shù)中的返回值,我們提前命名好了,本來應(yīng)該是返回結(jié)果為 1
可是在return
這里,執(zhí)行順序這樣的
res = 1
res++
因此,結(jié)果就是 2
關(guān)于“GO語言中defer實現(xiàn)原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。