golang 函數三 (延遲調用)

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

在要求高性能的高併發場景下,應避免使用延遲調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章