一、源頭:
早期管理資源(如數據庫連接、鎖、文件句柄、網絡連接)和狀態清理異常麻煩。
必須在每個可能的返回點(return、err、panic)手動重復清理代碼,極易遺漏且打斷主要邏輯思路!
像Java語言雖然用了Try-Catch,但缺點是邏輯不清晰、臃腫、不容易判斷錯誤出在什么地方。
作為新生代的Go,及眾多語言之精華,推出了defer處理機制。
尤其是在Go1.14版本時,性能開銷接近零,更無后顧之憂。
二、定義:
1、語法:
defer functionCall(..arguments..)
defer后面直接跟一個函數調用(可以是命名函數/匿名函數/方法...)
2、定義:
當函數執行到defer語句時(注冊時),他會立即求值此時該函數調用的參數并將此次函數調用(包括已求值的參數)放到一個延遲調用表中。這個調用函數與goroutine關聯,采用LIFO(后進先出的方式調用)。切記這個延遲調用表不會立即執行,而是會等到(函數真正結束之前--函數(return或panic)之后)在調用。
3、特性:
1、延遲執行:運行到defer時,只是將求值后的參數與調用的函數一并打包到延遲調用表中,需等到函數體結束之后在執行
2、LIFO方式執行:后進先出的方式執行
3、參數求值時機:defer 語句中的函數參數的值,是在執行到defer語句時(即注冊時)就確定并保存下來的,而不是在延遲函數實際執行時才求值。
4、作用域:自各函數體
三、應用:
1、臨近釋放:(邏輯清晰)
mu.Lock()
defer mu.Unlock() // 好習慣!確保解鎖
// ... 操作共享數據 ...
2、panic補獲:(防止程序崩潰)
特殊情況,根據源碼分析---協程中出現panic,若不能再該協程中捕獲,則會導致整個程序崩潰。
func test(){defer func(){if r := recover(); r!=nil{fmt.Println(r);}}()panic(1);
}
3、循環函數釋放:(利用完資源后,及時釋放資源)
// 正確做法:將文件處理封裝到函數,defer 在每次循環的匿名函數結束時執行
func outerFunc() {for _, filename := range filenames {func() { // 匿名函數f, err := os.Open(filename)if err != nil {log.Println(err)return // 退出匿名函數}defer f.Close() // 延遲到當前匿名函數結束時執行 (即本次循環結束)// ... 處理 f ...}() // 立即調用匿名函數}
}
4、查看執行順序:
代碼右上角,有個運行小按鈕,點擊運行查看。
package mainimport ("fmt""log"
)func g(i int) {if i > 1 {fmt.Println("Panicking!")panic(1)}defer fmt.Println("Defer in g", i)fmt.Println("Printing in g", i)g(i + 1)
}func f() {defer func() {if r := recover(); r != nil {log.Println("Recovered in f", r)}}()fmt.Println("Calling g.")g(0)fmt.Println("Returned normally from f.")
}func main() {f()
}
四、底層:
這個是我扒出來的底層源碼,重點了解heap、link這倆。
type _defer struct {heap bool //表示是分配在堆上還是棧上。rangefunc bool // true for rangefunc listsp uintptr // 棧指針pc uintptr // 程序計數器fn func() // 表示需要被延遲執行的函數。link *_defer // 指向下一個 _defer 結構體的指針。// If rangefunc is true, *head is the head of the atomic linked list// during a range-over-func execution.head *atomic.Pointer[_defer]
}
defer 語句注冊時,會創建一個 _defer 結構體實例。
多個 defer?通過 link 字段形成一個單鏈表(LIFO 棧),掛載到當前 goroutine 的結構上(g._defer)。新的 defer 總是插入鏈表頭部。
主要有兩大種分配方式。
1、堆棧分配
區別:分配位置的不同
獲取到runtime_defer結構體,它都會被追加到所在 Goroutine?_defer
?鏈表的最前面。
2、開放編碼
不建額外結構,直接把 defer 代碼塞到函數退出前,用位掩碼控制執行,開銷幾乎和普通調用一樣。
3、選擇:
首先考慮開放編碼(已經優化到:實際消耗跟調用普通函數差不多的地步),后棧分配,保底堆分配
以下是整理的Go版本迭代全史,有興趣的可以一看,挺有趣的。
📅?Go 版本迭代全史(2009–2025)
??早期階段
版本 | 發布時間 | 核心特性 |
---|---|---|
初始開源 | 2009-11-10 | 正式開源,獲得 TIOBE 年度語言稱號 |
Go r56 | 2011-03-16 | 首個穩定版本 |
Go 1.0 | 2012-03-28 | 首個正式版本,承諾向后兼容性;引入?go tool pprof 、go vet |
🔄?每半年發布周期(2013 年起)
版本 | 發布時間 | 核心特性 |
---|---|---|
Go 1.1 | 2013-05-13 | 重寫調度器(支持 Work-Stealing 算法);引入競態檢測器 |
Go 1.2 | 2013-12-01 | 支持全切片表達式;go test ?支持覆蓋率統計 |
Go 1.3 | 2014-06-18 | 棧模型改為連續棧;引入?sync.Pool |
Go 1.4 | 2014-12-10 | 支持 Android;運行時從 C 改為 Go;移除?src/pkg ?層級 |
Go 1.5 | 2015-08-19 | 自舉(移除 C 代碼);優化 GC(延遲降至 30ms);引入?vendor ?機制 |
Go 1.6 | 2016-02-17 | 默認支持 HTTP/2;GC 延遲進一步降低 |
Go 1.7 | 2016-08-15 | 引入?context ?包;SSA 后端優化(性能提升 5–35%) |
Go 1.8 | 2017-02-17 | GC 延遲降至亞毫秒級;defer ?性能提升 50% |
Go 1.9 | 2017-08-24 | 引入類型別名;新增并發安全的?sync.Map |
Go 1.10 | 2018-02-16 | 構建緩存(Build Cache)默認開啟 |
Go 1.11 | 2018-08-25 | 引入 Go Modules;支持 WebAssembly |
Go 1.12 | 2019-03-01 | 優化 TLS 1.3 支持;改進模塊機制 |
Go 1.13 | 2019-09-03 | 支持二進制/八進制字面量;錯誤處理增強(errors.Is/As/Unwrap ) |
Go 1.14 | 2020-02-25 | 接口允許方法集重疊;Goroutine 支持異步搶占調度;defer ?性能接近零開銷 |
Go 1.15 | 2020-08-11 | 優化鏈接器;改進內聯策略 |
Go 1.16 | 2021-02 | 支持靜態文件嵌入;默認啟用 Go Modules |
🚀?重大革新階段(2022–2025)
版本 | 發布時間 | 核心特性 |
---|---|---|
Go 1.18 | 2022-03-15 | 引入泛型;支持模糊測試(Fuzzing);工作區模式(Multi-Module Workspaces) |
Go 1.23 | 2025 年初 | 引入迭代器(seq /seq2 );gopls ?現代化工具鏈;go get ?管理工具鏈 |
Go 1.24 | 2025 年中 | 標準庫支持?strings/slices/maps ?迭代器;增強 WebAssembly 安全性與性能 |
Go 1.25 | 2025-08 (預計) | 移除核心類型(Core Types);簡化泛型規范;優化錯誤提示 |
太多了不好記,有興趣查看時,可以重點看:
1、初始開源-2009-11-10,go降生到了這個世界上
2、Go1.5 :2015,go開始用母語了,實現自舉(Go 編譯 Go),GC 延遲從 300ms 降至 30ms,奠定現代 Go 基礎
3、Go1.11:2018,引入了Go Module解決了依賴問題,讓現在的我都收益不止--今年2025
4、Go1.18:2022,泛型、模糊測試、工作區多模塊,等均進行了新功能的填充與優化。這個咱暫接觸不夠多,后期接觸了,會回來優化本篇博客
.....