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
在要求高性能的高并发场景下,应避免使用延迟调用。