《Go語言圣經》defer
核心概念:defer語句的執行時機
defer
是Go語言的一個關鍵字,它的作用是:延遲執行一個函數調用,該調用會在包圍它的函數返回前一刻執行。
關鍵點:
defer
語句會在函數即將返回時執行,無論函數是正常返回還是 panic 異常defer
語句按后進先出(LIFO)的順序執行defer
語句中的函數參數會在defer
語句定義時計算,而非執行時計算
代碼解析:trace函數與defer的配合
func bigSlowOperation() {defer trace("bigSlowOperation")() // 關鍵語句// ...lots of work...time.Sleep(10 * time.Second)
}func trace(msg string) func() {start := time.Now()log.Printf("enter %s", msg)return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) }
}
執行流程分析:
-
進入bigSlowOperation函數:
- 遇到
defer trace("bigSlowOperation")()
語句 - 首先執行
trace("bigSlowOperation")
函數調用 trace
函數的執行過程:- 記錄開始時間
start := time.Now()
- 打印日志
enter bigSlowOperation
- 返回一個匿名函數
func() { ... }
- 記錄開始時間
- 遇到
-
defer語句的處理:
defer
會將trace
函數返回的匿名函數放入延遲調用棧,但不會立即執行- 注意這里的兩層括號:
- 第一層
()
:調用trace
函數 - 第二層
()
:調用trace
返回的函數值
- 第一層
-
執行函數主體:
- 執行其他操作(示例中是睡眠10秒模擬耗時操作)
-
函數返回前:
- 從延遲調用棧中取出匿名函數并執行
- 匿名函數打印日志
exit bigSlowOperation (10s)
- 這里使用
time.Since(start)
計算函數執行耗時
為什么需要兩層括號?
這是理解這段代碼的關鍵:
-
trace("bigSlowOperation")
:這是調用trace
函數,返回一個func()
類型的值(即一個函數)。 -
第二層
()
:表示調用這個返回的函數。如果沒有這層括號:defer trace("bigSlowOperation") // 錯誤!
這會將
trace
函數本身放入延遲調用棧,而不是調用它返回的函數。這會導致:trace
函數會在bigSlowOperation
返回時才被調用(而不是進入時)- 永遠不會執行記錄退出的邏輯
類比理解:餐廳服務的比喻
可以將這個過程類比為餐廳服務:
bigSlowOperation
是"用餐過程"defer trace(...)()
的執行過程:- 進入餐廳時,服務員記錄你開始用餐(
trace
函數的前半部分) - 你點了一道需要長時間烹飪的菜(
time.Sleep
) - 用餐結束時,服務員記錄你離開的時間并計算用餐時長(
trace
返回的匿名函數)
- 進入餐廳時,服務員記錄你開始用餐(
擴展:記錄函數執行時間的完整示例
package mainimport ("log""time"
)// trace 記錄函數的進入和退出,并返回計算耗時的函數
func trace(msg string) func() {start := time.Now()log.Printf("進入函數: %s", msg)return func() {log.Printf("退出函數: %s (耗時: %v)", msg, time.Since(start))}
}// 模擬一個耗時操作
func complexCalculation() {defer trace("復雜計算")() // 關鍵語句,不要忘記兩層括號log.Println("正在執行復雜計算...")time.Sleep(2 * time.Second) // 模擬耗時操作log.Println("計算完成")
}func main() {log.Println("程序開始")complexCalculation()log.Println("程序結束")
}
輸出結果:
2025/06/18 15:00:00 程序開始
2025/06/18 15:00:00 進入函數: 復雜計算
2025/06/18 15:00:00 正在執行復雜計算...
2025/06/18 15:00:02 計算完成
2025/06/18 15:00:02 退出函數: 復雜計算 (耗時: 2s)
2025/06/18 15:00:02 程序結束
總結
這段代碼的核心技巧在于:
- 利用
defer
在函數退出時執行的特性 - 通過函數返回函數值(高階函數)的方式,將"進入記錄"和"退出記錄"綁定在一起
- 使用兩層括號分別完成"調用追蹤函數"和"調用返回的記錄函數"
這種模式在實際開發中非常有用,可以用于:
- 記錄函數執行日志
- 測量函數執行性能
- 資源釋放(如關閉文件、數據庫連接)
- 錯誤處理上下文管理
理解這種模式的關鍵是記住:defer
語句中的函數調用會在包圍函數返回時執行,而trace
函數的作用是生成這個延遲執行的函數。