網(wǎng)站首頁 編程語言 正文
GO 中 defer的實(shí)現(xiàn)原理
我們來回顧一下上次的分享,分享了關(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 是什么
咱們一起來看看 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)來,先出去
后進(jìn)來,后出去,不準(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)原理是咋樣的,我們還是一樣的,來看看 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è)條目 ,我們來看看上述數(shù)據(jù)結(jié)構(gòu)的參數(shù)都是啥意思
tag | 說明 |
---|---|
siz | defer函數(shù)的參數(shù)和結(jié)果的內(nèi)存大小 |
fn | 需要被延遲執(zhí)行的函數(shù) |
_panic | defer 的 panic 結(jié)構(gòu)體 |
link | 同一個(gè)協(xié)程里面的defer 延遲函數(shù),會(huì)通過該指針連接在一起 |
heap | 是否分配在堆上面 |
openDefer | 是否經(jīng)過開放編碼優(yōu)化 |
sp | 棧指針(一般會(huì)對(duì)應(yīng)到匯編) |
pc | 程序計(jì)數(shù)器 |
defer 關(guān)鍵字后面必須是跟函數(shù),這一點(diǎn)咱們要記住哦
通過上述參數(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ì)通過該指針連接在一起
這個(gè)link
指針,是指向的一個(gè)defer
單鏈表的頭,每次咱們聲明一個(gè)defer
的時(shí)候,就會(huì)將該defer
的數(shù)據(jù)插入到這個(gè)單鏈表頭部的位置,
那么,執(zhí)行defer
的時(shí)候,我們是不是就能猜到defer
是咋取得了不?
前面有說到defer
是后進(jìn)先出的,這里當(dāng)然也是遵循這個(gè)道理,取defer
進(jìn)行執(zhí)行的時(shí)候,是從單鏈表的頭開始去取的。
咱們來畫個(gè)圖形象一點(diǎn)
在協(xié)程A中聲明2個(gè)defer
,先聲明 defer test1()
再聲明 defer test2()
可以看出后聲明的defer
會(huì)插入到單鏈表的頭,先聲明的defer
被排到后面去了
咱們?nèi)〉臅r(shí)候也是一直取頭下來執(zhí)行,直到單鏈表為空。
咱一起來看看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è)延遲語句轉(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
來我們看看 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í)里面的成員也就是 咱們之前說到的 _defer
指針
其中 sched.deferpool[sc]
是全局的池子,pp.deferpool[sc]
是本地的池子
mallocgc分配空間
上述操作若 d 沒有拿到值,那么就直接使用 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)說這個(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 {}
最后再來看看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ù)會(huì)返回 1,此時(shí)GO 就會(huì)跳轉(zhuǎn)到執(zhí)行 deferreturn
簡(jiǎn)單說下 deferreturn
deferreturn
的作用就是情況defer
里面的鏈表,歸還相應(yīng)的緩沖區(qū),或者把對(duì)應(yīng)的空間讓GC
回收調(diào)
GO 中 defer 的規(guī)則
上面分析了GO 中defer
的實(shí)現(xiàn)原理之后,咱們現(xiàn)在來了解一下 GO 中應(yīng)用defer
是需要遵守 3 個(gè)規(guī)則的,咱們來列一下:
-
defer
后面跟的函數(shù),叫延遲函數(shù),函數(shù)中的參數(shù)在defer
語句聲明的時(shí)候,就已經(jīng)確定下來了 - 延遲函數(shù)的執(zhí)行時(shí)按照后進(jìn)先出來的,文章前面也多次說到過,這個(gè)印象應(yīng)該很深刻吧,先出現(xiàn)的
defer
后執(zhí)行,后出現(xiàn)的defer
先執(zhí)行 - 延遲函數(shù)可能會(huì)影響到整個(gè)函數(shù)的返回值
咱們還是要來解釋一下的,上面第 2 點(diǎn),應(yīng)該都好理解,上面的圖也表明了 執(zhí)行順序
第一點(diǎn)咱們來寫個(gè)小DEMO
延遲函數(shù)中的參數(shù)在defer
語句聲明的時(shí)候,就已經(jīng)確定下來了
func main() { num := 1 defer fmt.Println(num) num++ return }
別猜了,運(yùn)行結(jié)果是 1,小伙伴們可以將代碼拷貝下來,自己運(yùn)行一波
第三點(diǎn)也來一個(gè)DEMO
延遲函數(shù)可能會(huì)影響到整個(gè)函數(shù)的返回值
func test3() (res int) { defer func() { res++ }() return 1 } func main() { fmt.Println(test3()) return }
上述代碼,我們?cè)?test3
函數(shù)中的返回值,我們提前命名好了,本來應(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ī)則
原文鏈接:https://juejin.cn/post/6975686540601245709
- 上一篇:沒有了
- 下一篇:沒有了
相關(guān)推薦
- 2022-09-16 Python中的?No?Module?named?***問題及解決_python
- 2024-01-14 Spring boot 注解@Async不生效 無效 不起作用
- 2022-12-10 Qt界面中滑動(dòng)條的實(shí)現(xiàn)方式_C 語言
- 2022-11-19 Python?numpy?ndarray屬性,索引,切片_python
- 2022-03-09 C語言直接插入排序算法介紹_C 語言
- 2022-05-25 RedisTemplate實(shí)現(xiàn)setnx分布式鎖
- 2022-03-15 Docker Run Cadvisor failed: inotify_add_watch /sy
- 2022-07-01 Python判斷Nan值的五種方式小結(jié)_python
- 欄目分類
-
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支