Go語言提供defer關鍵字,用於延遲調用,延遲到當函數返回前被執行,多用於資源釋放、解鎖以及錯誤處理等操作。比如:
func main() { f, err := createFile("defer.txt") if err != nil { fmt.Println(err.Error()) return } defer closeFile(f) writeFile(f) } func createFile(filePath string) (*os.File, error) { f, err := os.Create(filePath) if err != nil { return nil, err } return f, nil } func writeFile(f *os.File) { fmt.Println("write file") fmt.Fprintln(f, "hello gopher!") } func closeFile(f *os.File) { fmt.Println("close file") f.Close() }
如果一個函數內引用了多個defer,它們的執行順序是怎麼樣的呢?比如:
package main func main() { defer println("a") defer println("b") } 輸出: b a
如果函數中引入了panic函數,那麼延遲調用defer會不會被執行呢?比如:
func main() { defer println("a") panic("d") defer println("b") } 輸出: a panic: d goroutine 1 [running]: panic(0x48a560, 0xc42000a340) /root/data/go/src/runtime/panic.go:500 +0x1a1 main.main() /root/data/workspace/src/defer/main.go:7 +0x107 exit status 2
日常開發中,一定要記住defer是在函數結束時才被調用的,如果應用不合理,可能會造成資源浪費,給gc帶來壓力,甚至造成邏輯錯誤,比如:
func main() { for i := 0;i < 10000;i++{ filePath := fmt.Sprintf("/data/log/%d.log", i) fp, err := os.Open(filePath) if err != nil{ continue } defef fp.Close() //這是要在main函數返回時纔會執行的,不是在循環結束後執行,延遲調用,導致佔用資源 //do stuff... } }
修改方案是直接調用Close函數或將邏輯封裝成獨立函數,比如:
func logAnalisys(p string){ fp, err := os.Open(p) if err != nil{ continue } defef fp.Close() //do stuff } func main() { for i := 0;i < 10000;i++{ filePath := fmt.Sprintf("/data/log/%d.log", i) logAnalisys(filePath) //將業務邏輯獨立封裝成函數 } }
在性能方面,延遲調用花費的代價也很大,因爲這個過程包括註冊、調用等操作,還有額外的內存開銷。比如:
package main import "testing" import "fmt" import "sync" var m sync.Mutex func test(){ m.Lock() m.Unlock() } func testCap(){ m.Lock() defer m.Unlock() } func BenchmarkTest(t *testing.B){ for i:= 0;i < t.N; i++{ test() } } func BenchmarkTestCap(t *testing.B){ for i:= 0;i < t.N; i++{ testCap() } } func main(){ resTest := testing.Benchmark(BenchmarkTest) fmt.Printf("BenchmarkTest \t %d, %d ns/op,%d allocs/op, %d B/op\n", resTest.N, resTest.NsPerOp(), resTest.AllocsPerOp(), resTest.AllocedBytesPerOp()) resTest = testing.Benchmark(BenchmarkTestCap) fmt.Printf("BenchmarkTestCap \t %d, %d ns/op,%d allocs/op, %d B/op\n", resTest.N, resTest.NsPerOp(), resTest.AllocsPerOp(), resTest.AllocedBytesPerOp()) } 輸出: BenchmarkTest 50000000, 27 ns/op,0 allocs/op, 0 B/op estCap 20000000, 112 ns/op,0 allocs/op, 0 B/op
在要求高性能的高併發場景下,應避免使用延遲調用。