標籤:

Golang裡面defer的執行順序為什麼是逆序的?

最近在看Go語言的教程,不能理解defer標記的方法為什麼要在所在語言塊結束後逆序執行,為什麼不是順序的呢?


舉個例子,如果我們的代碼邏輯是下面這樣的:

打開資料庫連接
defer 關閉連接

defer 刪除數據

因為一般defer定義是和打開連接並列的,打開文件,打開連接之後就定義了defer, 如果這之後你的defer是基於這個連接做的事情,那麼如果先進先執行的話就會錯誤了。這就是當初Go設計defer的時候考慮的問題。

這裡順帶提醒一下defer是存在一些小坑的,就是defer裡面的變數是申明的時候就copy的,不會隨著後面的函數邏輯改變而改變,除非你用指針類型。

package main

import "fmt"

func main() {

var whatever [5]struct{}

for i := range whatever {
fmt.Println(i)
}

for i := range whatever {
defer func() { fmt.Println(i) }()
}

for i := range whatever {
defer func(n int) { fmt.Println(n) }(i)
}

}

運行結果如下:

0
1
2
3
4
4
3
2
1
0
4
4
4
4
4


這個設計沒什麼特別之處吧,以C#的異常捕獲為例:

try {
// ....
try {
do_something();
} catch {
throw new Exception("xxooxxoo");
}
// ....
} catch {
// ....
}

內層的異常捕獲先被執行,然後再執行外層的。

至於異常捕獲為什麼這麼設計呢?可以想像一下資料庫事務的執行場景:

  1. 插入數據A

  2. 修改數據B = A + 1

  3. 刪除數據A
  4. 程序出錯

這時候正確的回滾應該是逆序的執行逆操作:

  1. 回退刪除A的操作

  2. 回退B的值

  3. 刪除數據A

如果按順序執行逆操作,事務就不能正常回退了:

  1. 刪除數據A

  2. 回退B的值

  3. 插入數據A

最後A被錯誤的保留下來了,事務完整性被破壞。

如果這是一條訂單記錄,這時候錢退了,訂單沒刪除。


因為defer一般是用來釋放資源的。比如關閉文件、釋放鎖等。從因果關係上後分配的資源可能會依賴前面的資源,如果先釋放被依賴的資源就會出現問題。所以倒過來執行不會破壞依賴關係。


一個小偷去倉庫偷東西,走的時候需要把腳印擦掉,你想一下他是怎麼把腳印擦掉的。

肯定是先擦最後留下來的。

進屋時:打開大門,內門,柜子,關上柜子,內門,大門。一切恢復原狀。

程序運行的時候基本上也是這樣。


你把它理解成一個後進先出的棧就行了


就是堆棧了,先進後出~第一個defer壓到棧底


stack的原理,先進後出。

函數的調用都是一樣的。

先進棧,後出棧


如果熟悉 C++,想像一下局部對象的析構函數。

或者想像成一個棧,定義是入棧過程,執行是依次從棧頂執行。


推薦閱讀:

如何看待Go進入tiobe top 20?
有哪些可以在500-1000行以內實現的Go語言項目?
有哪些不錯的golang開源項目?
go語言在變數創建前為什麼要聲明?

TAG:Go語言 |