GO語(yǔ)言中defer實(shí)現(xiàn)原理的示例詳解
GO 中 defer的實(shí)現(xiàn)原理
我們來(lái)回顧一下上次的分享,分享了關(guān)于 通道的一些知識(shí)點(diǎn)
- 分享了 GO 中通道是什么
- 通道的底層數(shù)據(jù)結(jié)構(gòu)詳細(xì)解析
- 通道在GO源碼中是如何實(shí)現(xiàn)的
- Chan 讀寫的基本原理
- 關(guān)閉通道會(huì)出現(xiàn)哪些異常,panic
- select 的簡(jiǎn)單應(yīng)用
要是對(duì) chan
通道還有點(diǎn)興趣的話,歡迎查看文章 GO 中 Chan 實(shí)現(xiàn)原理分享
defer 是什么
咱們一起來(lái)看看 defer
是個(gè)啥
是 GO 中的一個(gè)關(guān)鍵字
這個(gè)關(guān)鍵字,我們一般用在釋放資源,在 return
前會(huì)調(diào)用他
如果程序中有多個(gè) defer
,defer 的調(diào)用順序是按照類似棧的方式,后進(jìn)先出 LIFO
的 ,這里順便寫一下
棧
遵循后進(jìn)先出原則
后進(jìn)入棧的,先出棧
先進(jìn)入棧的,后出棧
隊(duì)列
遵循先進(jìn)先出 , 我們就可以想象一個(gè)單向的管道,從左邊進(jìn),右邊出
先進(jìn)來(lái),先出去
后進(jìn)來(lái),后出去,不準(zhǔn)插隊(duì)
defer 實(shí)現(xiàn)原理
咱們先拋出一個(gè)結(jié)論,先心里有點(diǎn)底:
代碼中聲明 defer
的位置,編譯的時(shí)候會(huì)插入一個(gè)函數(shù)叫做 deferproc
,在該defer
所在的函數(shù)前插入一個(gè)返回的函數(shù),不是return
哦,是deferreturn
具體的 defer
的實(shí)現(xiàn)原理是咋樣的,我們還是一樣的,來(lái)看看 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)用列表中的一個(gè)條目 ,我們來(lái)看看上述數(shù)據(jù)結(jié)構(gòu)的參數(shù)都是啥意思
tag | 說(shuō)明 |
---|---|
siz | defer函數(shù)的參數(shù)和結(jié)果的內(nèi)存大小 |
fn | 需要被延遲執(zhí)行的函數(shù) |
_panic | defer 的 panic 結(jié)構(gòu)體 |
link | 同一個(gè)協(xié)程里面的defer 延遲函數(shù),會(huì)通過(guò)該指針連接在一起 |
heap | 是否分配在堆上面 |
openDefer | 是否經(jīng)過(guò)開(kāi)放編碼優(yōu)化 |
sp | 棧指針(一般會(huì)對(duì)應(yīng)到匯編) |
pc | 程序計(jì)數(shù)器 |
defer 關(guān)鍵字后面必須是跟函數(shù),這一點(diǎn)咱們要記住哦
通過(guò)上述參數(shù)的描述,我們可以知道,defer
的數(shù)據(jù)結(jié)構(gòu)和函數(shù)類似,也是有如下三個(gè)參數(shù):
- 棧指針 SP
- 程序計(jì)數(shù)器 PC
- 函數(shù)的地址
可是我們是不是也發(fā)現(xiàn)了,成員里面還有一個(gè)link
,同一個(gè)協(xié)程里面的defer 延遲函數(shù),會(huì)通過(guò)該指針連接在一起
這個(gè)link
指針,是指向的一個(gè)defer
單鏈表的頭,每次咱們聲明一個(gè)defer
的時(shí)候,就會(huì)將該defer
的數(shù)據(jù)插入到這個(gè)單鏈表頭部的位置,
那么,執(zhí)行defer
的時(shí)候,我們是不是就能猜到defer
是咋取得了不?
前面有說(shuō)到defer
是后進(jìn)先出的,這里當(dāng)然也是遵循這個(gè)道理,取defer
進(jìn)行執(zhí)行的時(shí)候,是從單鏈表的頭開(kāi)始去取的。
咱們來(lái)畫個(gè)圖形象一點(diǎn)
在協(xié)程A中聲明2個(gè)defer
,先聲明 defer test1()
再聲明 defer test2()
可以看出后聲明的defer
會(huì)插入到單鏈表的頭,先聲明的defer
被排到后面去了
咱們?nèi)〉臅r(shí)候也是一直取頭下來(lái)執(zhí)行,直到單鏈表為空。
咱一起來(lái)看看defer 的具體實(shí)現(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)建一個(gè)新的遞延函數(shù) fn
,參數(shù)為 siz 字節(jié),編譯器將一個(gè)延遲語(yǔ)句轉(zhuǎn)換為對(duì)this
的調(diào)用
getcallersp()
:
得到deferproc
之前的rsp
寄存器的值,實(shí)現(xiàn)的方式所有平臺(tái)都是一樣的
//go:noescape func getcallersp() uintptr // implemented as an intrinsic on all platforms
callerpc := getcallerpc()
:
此處得到 rsp
之后,存儲(chǔ)在 callerpc
中 , 此處是為了調(diào)用 deferproc
的下一條指令
d := newdefer(siz)
:
d := newdefer(siz)
新建一個(gè)defer
的結(jié)構(gòu),后續(xù)的代碼是在給defer
這個(gè)結(jié)構(gòu)的成員賦值
咱看看 deferproc 的大體流程
- 獲取
deferproc
之前的rsp寄存器的值 - 使用
newdefer
分配一個(gè) _defer 結(jié)構(gòu)體對(duì)象,并且將他放到當(dāng)前的_defer
鏈表的頭 - 初始化_defer 的相關(guān)成員參數(shù)
- return0
來(lái)我們看看 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池,分配一個(gè)Defer
每個(gè)defer
可以自由的釋放。當(dāng)前defer
也不會(huì)加入任何一個(gè) 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
然后拿到 從全局的對(duì)象池子中拿一部分對(duì)象給到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) }
點(diǎn)進(jìn)去看池子的數(shù)據(jù)結(jié)構(gòu),其實(shí)里面的成員也就是 咱們之前說(shuō)到的 _defer
指針
其中 sched.deferpool[sc]
是全局的池子,pp.deferpool[sc]
是本地的池子
mallocgc分配空間
上述操作若 d 沒(méi)有拿到值,那么就直接使用 mallocgc
重新分配,且設(shè)置好 對(duì)應(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
具體實(shí)現(xiàn)在 src/runtime/malloc.go
中,若感興趣的話,可以深入看看這一塊,今天咱們不重點(diǎn)說(shuō)這個(gè)函數(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 {}
最后再來(lái)看看return0
最后再來(lái)看看 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)用,用來(lái)通知調(diào)用Go
的函數(shù)它不應(yīng)該跳轉(zhuǎn)到deferreturn
。
在正常情況下 return0
正常返回 0
可是異常情況下 return0
函數(shù)會(huì)返回 1,此時(shí)GO 就會(huì)跳轉(zhuǎn)到執(zhí)行 deferreturn
簡(jiǎn)單說(shuō)下 deferreturn
deferreturn
的作用就是情況defer
里面的鏈表,歸還相應(yīng)的緩沖區(qū),或者把對(duì)應(yīng)的空間讓GC
回收調(diào)
GO 中 defer 的規(guī)則
上面分析了GO 中defer
的實(shí)現(xiàn)原理之后,咱們現(xiàn)在來(lái)了解一下 GO 中應(yīng)用defer
是需要遵守 3 個(gè)規(guī)則的,咱們來(lái)列一下:
defer
后面跟的函數(shù),叫延遲函數(shù),函數(shù)中的參數(shù)在defer
語(yǔ)句聲明的時(shí)候,就已經(jīng)確定下來(lái)了- 延遲函數(shù)的執(zhí)行時(shí)按照后進(jìn)先出來(lái)的,文章前面也多次說(shuō)到過(guò),這個(gè)印象應(yīng)該很深刻吧,先出現(xiàn)的
defer
后執(zhí)行,后出現(xiàn)的defer
先執(zhí)行 - 延遲函數(shù)可能會(huì)影響到整個(gè)函數(shù)的返回值
咱們還是要來(lái)解釋一下的,上面第 2 點(diǎn),應(yīng)該都好理解,上面的圖也表明了 執(zhí)行順序
第一點(diǎn)咱們來(lái)寫個(gè)小DEMO
延遲函數(shù)中的參數(shù)在defer
語(yǔ)句聲明的時(shí)候,就已經(jīng)確定下來(lái)了
func main() { num := 1 defer fmt.Println(num) num++ return }
別猜了,運(yùn)行結(jié)果是 1,小伙伴們可以將代碼拷貝下來(lái),自己運(yùn)行一波
第三點(diǎn)也來(lái)一個(gè)DEMO
延遲函數(shù)可能會(huì)影響到整個(gè)函數(shù)的返回值
func test3() (res int) { defer func() { res++ }() return 1 } func main() { fmt.Println(test3()) return }
上述代碼,我們?cè)?test3
函數(shù)中的返回值,我們提前命名好了,本來(lái)應(yīng)該是返回結(jié)果為 1
可是在return
這里,執(zhí)行順序這樣的
res = 1
res++
因此,結(jié)果就是 2
總結(jié)
- 分享了defer是什么
- 簡(jiǎn)單示意了棧和隊(duì)列
- defer的數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn)原理,具體的源碼展示
- GO中defer的 3 條規(guī)則
以上就是GO語(yǔ)言中defer實(shí)現(xiàn)原理的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于GO語(yǔ)言defer的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入理解gorm如何和數(shù)據(jù)庫(kù)建立連接
這篇文章主要為大家詳細(xì)介紹了gorm如何和數(shù)據(jù)庫(kù)建立連接,文中的示例代碼講解詳細(xì),對(duì)我們深入了解GO語(yǔ)言有一定的幫助,需要的小伙伴可以參考下2023-11-11基于Golang實(shí)現(xiàn)Redis協(xié)議解析器
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)GO語(yǔ)言編寫簡(jiǎn)單的Redis協(xié)議解析器,文中的示例代碼講解詳細(xì),對(duì)我們深入了解Go語(yǔ)言有一定的幫助,需要的可以參考一下2023-03-03Go語(yǔ)言使用組合的思想實(shí)現(xiàn)繼承
這篇文章主要為大家詳細(xì)介紹了在 Go 里面如何使用組合的思想實(shí)現(xiàn)“繼承”,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以了解一下2022-12-12一個(gè)Pod調(diào)度失敗后重新觸發(fā)調(diào)度的所有情況分析
這篇文章主要為大家介紹了一個(gè)Pod調(diào)度失敗后重新觸發(fā)調(diào)度的所有情況分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04