Golang defer的運行時機和遇到的坑

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

一、defer概述

defergolang 中專屬的流程式控制制語句,用於延遲指定語句的運行時機,只能運行於函數的內部,且當他所屬函數運行完之後它才會被調用。例如:

1
2
3
4
func deferTest(){
defer fmt.Println("HelloDefer")
fmt.Println("HelloWorld")
}

它會先列印出HelloWorld ,然後再列印出HelloDefer

一個函數中如果有多個defer ,運行順序和函數中的調用順序相反,因為它們都是被寫在了棧中:

1
2
3
4
5
func deferTest(){
defer fmt.Println("HelloDefer1")
defer fmt.Println("HelloDefer2")
fmt.Println("HelloWorld")
}

運行結果:

1
2
3
fmt.Println("HelloDefer2")
fmt.Println("HelloDefer1")
fmt.Println("HelloWorld")

二、defer和return

在包含有return 語句的函數中,defer 的運行順序位於return 之後,但是defer 所啟動並執行程式碼片段會生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main(){
fmt.Println(deferReturn)
}
func deferReturn() int{
i := 1
defer func(){
fmt.Println("Defer")
i += 1
}()
return func()int{
fmt.Println("Return")
return i
}()
}

運行結果:

1
2
3
Return
Defer
1

這裡很明顯就能看到defer 是在return 之後啟動並執行!但是有一個問題是defer 裡執行了語句i +=1 ,按照這個邏輯的話返回的i 值應該是2 而不是1 。這個問題是由於return 的運行機制導致的:return 在返回一個對象時,如果傳回型別不是指標或者參考型別,那麼return 返回的就不會是這個對象本身,而是這個對象的副本。

我們可以驗證這一個觀點:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main(){
...
fmt.Println("main: ", x, &x)
}
func deferReturn() int{
...
defer ...{
fmt.Println("Defer: ", i, &i)
...
}()
return ...{
fmt.Println("Return: ", i, &i)
...
}()
}

程式的輸出為:

1
2
3
Return:     1 0xc042008238
Defer: 1 0xc042008238
main: 1 0xc042008230 //main函數中的i的地址和deferReturn()中的i的地址是不一樣的

如果把函數的傳回值改成指標類型,這時候的main函數中的傳回值就會和函數體內的一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main(){
x := deferReturn()
fmt.Println("main: ", x, *x)
}
func deferReturn()*int{
i := 1
p := &i
defer func() {
*p += 1
fmt.Println("defer: ", p, *p)
}()
return func() *int{
fmt.Println("Return: ", p, *p)
return p
}()
}

結果:

1
2
3
Return:     0xc0420361d0 1
defer: 0xc0420361d0 2
main: 0xc0420361d0 2

三、defer和panic

panic 會在defer 運行完之後才把恐慌擴散到其他函數:

1
2
3
4
func deferPanic(){
defer fmt.Println("HelloDefer")
panic("Hey, I"m panic")
}

結果:

1
2
3
4
5
6
7
HelloDefer  //會先輸出defer部分的代碼
panic: Hey, I"m panic
goroutine 1 [running]:
main.deferTest()
E:/code/golang/src/test_src/defer/main.go:12 +0xfc
main.main()
E:/code/golang/src/test_src/defer/main.go:6 +0x27

四、defer和for迴圈

不要在defer 內使用外部變數,可能會造成一些意想不到的錯誤:

1
2
3
4
5
6
7
func deferTest(){
for i := 0; i < 5; i ++{
defer func(){
fmt.Printf("%d", i)
}()
}
}

它的輸出的結果是55555 ,原理很簡單,因為defer會在for迴圈運行完後才會調用,for迴圈運行完時i的值為5,所以列印的i值會是55555

但是如果迴圈內的延時函數有參數傳入,參數就會在當前defer 語句執行的時候求出:

1
2
3
4
5
6
7
func deferTest(){
for i := 0; i < 5; i ++{
defer func(i int){
fmt.Printf("%d", i)
}(i)
}
}

此時就會列印出43210 而不是55555 了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.