拒絕濫用golang defer機制
來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。[原文連結](http://www.bugclosed.com/post/17) : http://www.bugclosed.com/post/17## defer機制go語言中的defer提供了在函數返回前執行操作的機制,在需要資源回收的情境非常方便易用(比如檔案關閉,socket連結資源十分,資料庫回話關閉回收等),在定義資源的地方就可以設定好資源的操作,代碼放在一起,減小忘記引起記憶體流失的可能。defer機制雖然好用,但卻不是免費的,首先效能會比直接函數調用差很多;其次,defer機制中傳回值求值也是一個容易出錯的地方。## 一個簡單的效能對比測試通過一個對鎖機制的defer操作來比較效能差異。```package mainimport ("sync""testing")var (lock = new(sync.Mutex))func lockTest() {lock.Lock()lock.Unlock()}func lockDeferTest() {lock.Lock()defer lock.Unlock()}func BenchmarkTest(b *testing.B) {for i := 0; i < b.N; i++ {lockTest()}}func BenchmarkTestDefer(b *testing.B) {for i := 0; i < b.N; i++ {lockDeferTest()}}```運行命令go test -v -test.bench, 效能對比測試結果如下:```BenchmarkTest-8 100000000 18.5 ns/opBenchmarkTestDefer-8 20000000 56.4 ns/op```從測試結果可以看出,Defer版本的lock操作時間消耗幾乎是函數直接調用的3倍以上。## defer執行順序和傳回值求值看一個簡單的測試:```package mainimport ("fmt")func test_unnamed()(int) {var i intdefer func() {i++fmt.Println("defer a:", i) }()defer func() {i++fmt.Println("defer b :", i) }()return i}func test_named()(i int) {defer func() {i++fmt.Println("defer c:", i) }()defer func() {i++fmt.Println("defer d :", i) }()return i}func main() {fmt.Println("return:", test_unnamed()) fmt.Println("return:", test_named()) }```執行結果是:```defer b : 1defer a: 2return: 0defer d : 1defer c: 2return: 2```關於同時有多個defer時的執行順序,可以看做是go編譯器為每個函數維護了一個先進後出的堆棧。每次遇到defer語句就講執行體封裝後壓入堆棧中,等到函數返回時,從堆棧中依次出棧執行。所以 “defer b”語句在後,卻先調用。關於函數求值問題,可以將test_unnamed函數返回和defer的執行和求值理解為3個步驟:1. 運行到“return i“語句時,取值當前i值,賦值給test_unnamed傳回值,得到函數test的傳回值0(因為test_unnamed中只定義了i,並未操作,i保留成初始預設值)。2. 按照先進後出的方式,一次調用defer語句執行。3. 執行真正的test_unnamed 函數返回 ”return“。以上是分析了匿名傳回值的情況,具名傳回值test_named的情況稍有不同,return 返回了2,而不是0,因為defer函數中對傳回值變數i做了修改。由此可見,使用多個defer和defer函數中還需要處理傳回值的情況下極容易出問題,使用時需要小心謹慎。## defer釋放鎖通過defer釋放鎖(sync.Mutex)是很常見的情境,樣本如下:```def GetMapData(key uint32) uint32{lock.Lock()defer lock.Unlock()if v, ok := mapData[key]; ok{return v}return 0}```在這樣簡單的情境下,通過defer直接釋放鎖,在後續的代碼邏輯基本可以忘記鎖的存在而寫代碼。但是這種模式就存在一個鎖粒度的問題--整個函數都被鎖住了。如果lock後面還有很多複雜或者阻塞的邏輯(寫日誌,訪問資料庫,從ch讀取資料等),會導致鎖的持有時間過大,影響系統的處理效能;此時可以精細控制邏輯函數的分拆,讓鎖盡量只控制共用資源,拋棄defer自行控制unlock,以免鎖粒度過大。## 總結defer是一個很強大的機制,尤其是在資源釋放的情境特別適用。但是使用時要注意,defer是有不小的效能損耗,且過度使用後也會導致邏輯變複雜。351 次點擊