原文鏈接 : http://www.bugclosed.com/post/17
defer機制
go語言中的defer提供了在函數返回前執行操作的機制,在需要資源回收的場景非常方便易用(比如文件關閉,socket鏈接資源十分,數據庫回話關閉回收等),在定義資源的地方就可以設置好資源的操作,代碼放在一起,減小忘記引起內存泄漏的可能。
defer機制雖然好用,但卻不是免費的,首先性能會比直接函數調用差很多;其次,defer機制中返回值求值也是一個容易出錯的地方。
一個簡單的性能對比測試
通過一個對鎖機制的defer操作來比較性能差異。
package main
import (
"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/op
BenchmarkTestDefer-8 20000000 56.4 ns/op
從測試結果可以看出,Defer版本的lock操作時間消耗幾乎是函數直接調用的3倍以上。
defer執行順序和返回值求值
看一個簡單的測試:
package main
import (
"fmt"
)
func test_unnamed()(int) {
var i int
defer 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 : 1
defer a: 2
return: 0
defer d : 1
defer c: 2
return: 2
關於同時有多個defer時的執行順序,可以看做是go編譯器為每個函數維護了一個先進后出的堆棧。每次遇到defer語句就講執行體封裝后壓入堆棧中,等到函數返回時,從堆棧中依次出棧執行。所以 “defer b”語句在后,卻先調用。
關於函數求值問題,可以將test_unnamed函數返回和defer的執行和求值理解為3個步驟:
- 運行到“return i“語句時,取值當前i值,賦值給test_unnamed返回值,得到函數test的返回值0(因為test_unnamed中只定義了i,並未操作,i保留成初始默認值)。
- 按照先進后出的方式,一次調用defer語句執行。
- 執行真正的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是有不小的性能損耗,且過度使用后也會導致邏輯變復雜。