Go關(guān)鍵字defer的使用和底層實現(xiàn)
1:defer是什么
defer是Go語言的關(guān)鍵字,一般用于資源的釋放和異常的捕捉(比如:文件打開、加鎖、數(shù)據(jù)庫連接、異常捕獲),defer語句后將其后面跟隨的語句進(jìn)行延遲處理,就是說在函數(shù)執(zhí)行完畢后再執(zhí)行調(diào)用,也就是return的ret指令之前。
1.1 資源釋放
資源的釋放在代碼中有很多場景,比如打開文件描述符資源后,需要進(jìn)行file.close得到釋放,在打開文件后就加上defer,避免在后續(xù)因為err導(dǎo)致的return退出忘記釋放,文件資源。
func openFile() { file, err := os.Open("txt") if err != nil { return } defer file.Close() //合理位置 }
常見的加鎖場景,業(yè)務(wù)代碼中忘記釋放鎖,那么會導(dǎo)致資源得不到釋放,造成死鎖,但是defer就很好解決了這個問題,不管業(yè)務(wù)邏輯怎么處理,最終還是會釋放鎖。
func lockScene() { var mutex sync.Mutex mutex.Lock() defer mutex.Unlock() //業(yè)務(wù)代碼... }
1.2 捕獲異常
Go 語言中 recover 關(guān)鍵字主要用于捕獲異常,讓程序回到正常狀態(tài)。recover 可以中止 panic 造成的程序崩潰。它是一個只能在 defer 中發(fā)揮作用的函數(shù),在其他作用域中調(diào)用不會發(fā)揮作用
func demo() { defer func() { if err := recover(); err !=nil{ fmt.Println(string(Stack())) } }() panic("unknown") }
2:defer語法
defer語法相對簡單,直接在普通函數(shù)之前加一個defer關(guān)鍵字
defer demoFunc(args)
雖然defer語法簡單,但是當(dāng)有多個defer注冊時,會以逆序執(zhí)行(類似棧:先進(jìn)后出),舉個栗子。
func f1() { defer fmt.Println("defer1") defer fmt.Println("defer2") fmt.Println("start") fmt.Println("end") return }
這段代碼的字符串輸出結(jié)果是:start、end、defer2、defer1。首先輸出的defer字符串在正常的start、end后輸出可以很好理解(defer在函數(shù)返回前執(zhí)行),字符串defer2在defer1前輸出(逆序執(zhí)行)。
3:defer與return
Go語言中函數(shù)的 return 語句并不是原子級的,實際的執(zhí)行過程為為設(shè)置返回值—>ret指令,defer 語句是在返回前執(zhí)行,所以返回過程是:「設(shè)置返回值—>執(zhí)行defer—>ret」
4:defer底層實現(xiàn)
要了解defer的實現(xiàn),先看下defer的底層數(shù)據(jù)結(jié)構(gòu)和各個參數(shù)表示的意義(src/runtime/runtime2.go)
type _defer struct { siz int32 // 參數(shù)和返回值的內(nèi)存大小 started bool heap bool //是否分配在堆上面 openDefer bool // 是否經(jīng)過開放編碼優(yōu)化 sp uintptr // sp 計數(shù)器值,棧指針 pc uintptr // pc 計數(shù)器值,程序計數(shù)器 fn *funcval // defer 傳入的函數(shù)地址,也就是延后執(zhí)行的函數(shù) _panic *_panic // defer 的 panic 結(jié)構(gòu)體 link *_defer // 同一個協(xié)程里面的defer 延遲函數(shù),會通過該指針連接在一起 }
defer怎么實現(xiàn)延遲的
通過資料了解到,defer在代碼中的位置在編譯后會有兩部分內(nèi)容: 1:deferproc負(fù)責(zé)把要執(zhí)行的函數(shù)保存起來,我們稱之為defer注冊 2:deferreturn是在defer注冊完成(deferproc)后,程序執(zhí)行后續(xù)業(yè)務(wù)代碼,直到通過deferreturn執(zhí)行注冊的defer函數(shù)
為啥是逆序執(zhí)行
defer結(jié)構(gòu)有個link指針,是指向的一個defer單鏈表的頭,每次咱們聲明一個defer的時候,就會將該defer的數(shù)據(jù)插入到這個單鏈表頭部的位置,取defer進(jìn)行執(zhí)行的時候,是從單鏈表的頭開始去取的,這就是defer先進(jìn)后出的原因。 底層代碼在src/runtime/panic.go,核心代碼做了說明
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn gp := getg() //獲取goroutine結(jié)構(gòu) if gp.m.curg != gp { // go code on the system stack can't defer throw("defer on system stack") } ... d := newdefer(siz) //新建一個defer結(jié)構(gòu) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } d.link = gp._defer // 新建defer的link指針指向g的defer gp._defer = d // 新建defer放到g的defer位置,完成插入鏈表表頭操作 d.fn = fn d.pc = callerpc d.sp = sp ... }
如圖:先聲明defer fun1()、再聲明 defer fun2(),fun2()在單鏈表鏈表頭部。
總結(jié)
1:defer關(guān)鍵字后面必須是函數(shù),也叫延遲函數(shù)
2:defer是逆序執(zhí)行(后進(jìn)先出),延遲函數(shù)中的參數(shù)在defer聲明的時候已經(jīng)確定了
3:在函數(shù)return之前執(zhí)行延遲函數(shù)
到此這篇關(guān)于Go關(guān)鍵字defer的使用和底層實現(xiàn)的文章就介紹到這了,更多相關(guān)Go defer使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GO項目實戰(zhàn)之Gorm格式化時間字段實現(xiàn)
GORM自帶的time.Time類型JSON默認(rèn)輸出RFC3339Nano格式的,下面這篇文章主要給大家介紹了關(guān)于GO項目實戰(zhàn)之Gorm格式化時間字段實現(xiàn)的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01深入理解Go gin框架中Context的Request和Writer對象
這篇文章主要為大家詳細(xì)介紹了Go語言的gin框架中Context的Request和Writer對象,文中的示例代碼講解詳細(xì),對我們深入了解Go語言有一定的幫助,快跟隨小編一起學(xué)習(xí)一下吧2023-04-04go語言生成隨機(jī)數(shù)和隨機(jī)字符串的實現(xiàn)方法
隨機(jī)數(shù)在很多時候都可以用到,尤其是登錄時,本文就詳細(xì)的介紹一下go語言生成隨機(jī)數(shù)和隨機(jī)字符串的實現(xiàn)方法,具有一定的參考價值,感興趣的可以了解一下2021-12-12Golang報“import cycle not allowed”錯誤的2種解決方法
這篇文章主要給大家介紹了關(guān)于Golang報"import cycle not allowed"錯誤的2種解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以們下面隨著小編來一起看看吧2018-08-08Go如何實現(xiàn)Websocket服務(wù)以及代理
這篇文章主要介紹了Go如何實現(xiàn)Websocket服務(wù)以及代理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04