go defer (go延遲函數)
Go語言的defer算是一個語言的新特性,至少對比當今主流編程語言如此。根據GO LANGUAGE SPEC的說法:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking. defer語句調用一個函數,這個函數執行會推遲,直到外圍的函數返回,或者外圍函數運行到最后,或者相應的goroutine panic
換句話說,每當defer執行的時候,它后面的函數值(在go中函數是一個引用類型,是一等公民,可以賦值給變量)和函數參數會被求值,但是函數不會立即調用,直到(↑)上述三種情況發生。 這就是defer的全部內容,沒了,剩下就是defer的best practice
函數不會立即調用
先從最簡單的開始:
func readFile(fileName string){
f,err := os.Open(fileName)
if err!=nil {
return
}
defer f.Close()
var content [1024]byte
f.Read(content[:])
fmt.Printf("%s",content)
}
func main() {
readFile("test.data")
}
程序輸出test.data前1024字節的內容。值得一提的是,類似這種open/close配對操作是defer的慣用法。 這個例子詮釋了上面那句話的后半段
"但是函數不會被調用"
因為如果defer后面的f.Close()沒有延遲執行,那么文件描述符都關閉了,就不會讀取到任何內容。
函數值和函數參數被求值,但函數不會立即調用
下面這個例子即將詮釋上半段,它來自<>,稍作修改:
func trace(funcName string) func(){
start := time.Now()
fmt.Printf("function %s enter\n",funcName)
return func(){
log.Printf("function %s exit (elapsed %s)",funcName,time.Since(start))
}
}
func foo(){
defer trace("foo()")()
time.Sleep(5*time.Second)
}
func main(){
foo()
foo()
}
/*
OUTPUT:
function foo() enter
function foo() exit (elapsed 5.0095471s)
function foo() enter
function foo() exit (elapsed 5.0005382s)
*/
為什么foo會輸出enter然后等待五秒左右再輸出exit? 因為正如我們說的,
defer后面的函數值和參數會被求值但是實際函數調用卻要等到最后
這里函數值就是trace()返回的匿名函數,函數參數當然就是字符串字面值"foo()", 對trace("foo()")的求值會輸出function foo() enter, 實際函數調用trace("foo()")()即輸出function foo() exit(elapsed x.x)會推遲到return執行(如果return會更新返回值變量,則會在更新后才執行defer的函數)。
雜項
多說一點,如果存在多個defer語句,最后的defer的函數的執行順序與defer出現的順序相反,如:
func main() {
func1 := func(){
fmt.Println("func1() execution deferred")
}
func2 := func(){
fmt.Println("func2() execution deferred")
}
defer func1()
defer func2()
fmt.Println("strat\nworking...")
}
/*
OUTPUT:
strat
working...
func2() execution deferred
func1() execution deferred
*/
