網站首頁 編程語言 正文
golang的defer
什么是defer
defer的的官方文檔:https://golang.org/ref/spec#Defer_statements
go語言中defer可以完成延遲功能,當前函數執行完成后再執行defer的代碼塊。通過defer,我們可以在代碼中優雅的關閉/清理代碼中所使用的變量。
defer是Go語言中的延遲執行語句,用來添加函數結束時執行的代碼,常用于釋放某些已分配的資源、關閉數據庫連接、斷開socket連接、解鎖一個加鎖的資源。
Go語言機制擔保一定會執行defer語句中的代碼。其它語言中也有類似的機制,比如Java、C#語言里的finally語句,C++語言里的析構函數(Destructor)可以起類似的作用,C++語言機制擔保在對象被銷毀前一定會執行析構函數中的代碼。C++中的析構函數析構的是對象,Go中的defer析構的是函數。
理解defer
defer什么時間執行(defer、 return、返回值 三者的執行順序)
defer只有在當前函數執行完畢后,才會執行。描述其實不太精確
go中的return語句并不是原子性操作,一般是分為兩步:
- 將返回值賦值給一個變量
- 執行RET指令
return并不是原子性操作,是通過一個變量賦值和ret指令來完成的。defer就執行在1之后,2之前。defer的執行順序在return之后,但是在返回值返回給調用方之前,所以使用defer可以達到修改返回值的目的。
defer、 return、返回值 三者的執行順序是 : return 最先給返回值賦值;接著 defer 開始執行一些收尾工作;最后 RET 指令攜帶返回值退出函數。
package main import ( "fmt" ) func main() { ret := test() fmt.Println("test return:", ret) } // func test() ( int) { 這種就是匿名返回值 //返回值改為命名返回值, 具名返回值。即返回值帶有名字, 這樣我們在執行defer的時候相當于修改了返回值的值 func test() (i int) { //var i int defer func() { i++ fmt.Println("test defer, i = ", i) }() return i }
注意: 這塊驗證使用了具名返回值
func test() (i int) {
中的(i int)
。 測試結果滿足我們預期。
編碼中,我們要特別注意, go語言中匿名返回值和命名返回值對defer的影響。不過一般我們都是使用命名返回值。
一個主函數擁有一個匿名的返回值,返回時使用字面值,比如返回”1”、”2”、”Hello”這樣的值,這種情況下defer語句是無法操作返回值的。
defer輸出的值,就是定義時的值。而不是defer真正執行時的變量值(注意引用情況)
defer函數會在return之后被調用。那么這段函數執行完之后,是不用應該輸出1呢?
package main import "fmt" func test1() { i := 0 defer fmt.Println(i) i++ return } func main() { test1() }
輸出結果:0
雖然我們在defer后面定義的是一個帶變量的函數: fmt.Println(i). 但這個變量(i)在defer被聲明的時候,就已經確定其確定的值了。
總結: 因為defer后面的函數在入棧的時候保存的是入棧那一刻的值,而當時i的值是0,所以后期對i修改,并不會影響棧內函數的值。
我們再看下一個例子:
package main import "fmt" func test2() { x := 10 defer func(a *int) { fmt.Println(*a) }(&x) x++ } func main() { test2() }
輸出結果: 11
這里為什么和前面結論不一樣呢?
這里defer后面函數入棧的時候存入的執行變量x的指針。所以,后期x值改變的時候,輸出結果也會改變。
總結: 需要注意引用情況。對于指針類型參數,規則仍然適用,只不過延遲函數的參數是一個地址值,這種情況下,defer后面的語句對變量的修改會影響延遲函數。
多個defer,執行順序
package main import ( "fmt" ) func main() { defer fmt.Println("main defer1") test() defer fmt.Println("main defer2") } func test() () { defer func() { fmt.Println("test defer1") }() defer func() { fmt.Println("test defer2") }() }
輸出結果:
test defer2
test defer1
main defer2
main defer1
總結: 后進先出(LIFO)的順序執行,即先出現的 defer 最后執行。即:多個defer語句的執行順序是逆序執行。
defer的函數一定會執行么?
defer是Go語言中的延遲執行語句,用來添加函數結束時執行的代碼,常用于釋放某些已分配的資源、關閉數據庫連接、斷開socket連接、解鎖一個加鎖的資源。
Go語言機制擔保一定會執行defer語句中的代碼。其它語言中也有類似的機制,比如Java、C#語言里的finally語句,C++語言里的析構函數(Destructor)可以起類似的作用,C++語言機制擔保在對象被銷毀前一定會執行析構函數中的代碼。C++中的析構函數析構的是對象,Go中的defer析構的是函數。
panic情況
網上demo:
package main import ( "fmt" ) func test1() { fmt.Println("test") } func test2() { panic(1) } func main() { fmt.Println("main start") defer test1() test2() //造panic fmt.Println("main end") }
執行結果:
main start
test ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
panic: 1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
goroutine 1 [running]: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
main.test2(...) ? ? ? ??
總結: 我們發現正常的panic,還是會調我們的defer的,并且在會在panic之前執行。
os.Exit情況
網上demo:
package main import ( "fmt" "os" ) func test1() { fmt.Println("test") } func main() { fmt.Println("main start") defer test1() fmt.Println("main end") os.Exit(0) }
執行結果:
main start
main end
總結: 如果在當前函數里是因為執行了os.Exit退出,而不是正常return退出或者panic退出,那程序會立即停止,被defer的函數調用不會執行。
kill情況(Ctrl+C)
package main import ( "fmt" "time" ) func test1() { fmt.Println("test") } func test2() { time.Sleep(60 * time.Second) } func main() { fmt.Println("main start") defer test1() test2() fmt.Println("main end") }
執行結果:
main start
Process finished with the exit code -1073741510 (0xC000013A: interrupted by Ctrl+C)
如上,我們test2()
睡眠時間內,點擊Ctrl+C
,發現defer test1()
并沒有執行。
總結:這個點很重要,需要我們在日常異常中斷時,留意defer是否未處理的情況。
所以一般情況下,我們程序需要捕獲這種異常中斷,在程序退出前,手動做一些處理。
參考文獻
Go常見坑:Go語言里被defer的函數一定會執行么?
參考URL: https://blog.csdn.net/perfumekristy/article/details/121343642
面試官:聽說你精通golang的defer?
參考URL: https://cloud.tencent.com/developer/article/2076951
原文鏈接:https://blog.csdn.net/inthat/article/details/127470602
相關推薦
- 2022-09-05 卷積層計算量(FLOPS)和參數量的計算
- 2022-03-09 C++之const限定符詳解_C 語言
- 2023-07-08 keycloak更新token調用updateToken函數無效,解決辦法
- 2023-01-17 Python使用鄰接矩陣實現圖及Dijkstra算法問題_python
- 2022-07-07 圖解AVL樹數據結構輸入與輸出及實現示例_C 語言
- 2022-11-17 Python中的優先隊列(priority?queue)和堆(heap)_python
- 2022-09-25 FFmpeg源碼分析:SwsContext圖像轉換上下文
- 2022-08-13 DHCP服務簡介及Linux配置實例
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支