首頁  >  文章  >  後端開發  >  在Go中什麼是defer?怎麼用?

在Go中什麼是defer?怎麼用?

藏色散人
藏色散人轉載
2021-09-01 15:52:432557瀏覽

本文由go語言教學專欄給大家介紹,主題是關於go defer的學習使用,希望對需要的朋友有幫助!

什麼是defer?

在Go中,一個函數呼叫可以跟在一個defer關鍵字後面,形成一個延遲函數呼叫。
當一個函數呼叫被延遲後,它不會立即被執行。它將被推入由當前協程維護的一個延遲呼叫堆疊。當函數呼叫(可能是也可能不是延遲呼叫)返回並進入它的退出階段後,所有在此函數呼叫中已經被推入的延遲呼叫將被按照它們被推入堆疊的順序逆序執行。當所有這些延遲呼叫執行完畢後,此函數呼叫也就真正退出了。
舉個簡單的例子:

package mainimport "fmt"func sum(a, b int) {
    defer fmt.Println("sum函数即将返回")
    defer fmt.Println("sum函数finished")
    fmt.Printf("参数a=%v,参数b=%v,两数之和为%v\n", a, b, a+b)}func main() {
    sum(1, 2)}

output:

参数a=1,参数b=2,两数之和为3
sum函数finished
sum函数即将返回

事實上,每個協程都維護著兩個呼叫堆疊。

  • 一個是正常的函數呼叫堆疊。在此堆疊中,相鄰的兩個呼叫存在著呼叫關係。晚進入堆疊的呼叫被早進入堆疊的呼叫所呼叫。此堆疊中最早被推入的呼叫是對應協程的啟動呼叫。
  • 另一個堆疊是上面提到的延遲呼叫堆疊。處於延遲呼叫堆疊中的任意兩個呼叫之間不存在呼叫關係。

defer函數參數估值

  • #對於一個延遲函數調用,它的實參是在此調用被推入延遲呼叫堆疊的時候被估值的。
  • 一個匿名函數體內的表達式是在此函數被執行的時候才會被逐個估值的,不管此函數是被普通呼叫還是延遲呼叫。
    範例1:
package mainimport  "fmt"func  Print(a int) {fmt.Println("defer函数中a的值=", a)}func  main() {a := 10defer  Print(a)a = 1000fmt.Println("a的值=", a)}

output:

a的值= 1000
defer函数中a的值= 10

defer Print(a) 被加入到延遲呼叫堆疊的時候,a 的值是5,故defer Print(a ) 輸出的結果為5
範例2:

package mainimport "fmt"func main() {
    func() {
        for i := 0; i < 3; i++ {
            defer fmt.Println("a=", i)
        }
    }()

    fmt.Println()
    func() {
        for i := 0; i < 3; i++ {
            defer func() {
                fmt.Println("b=", i)
            }()
        }
    }()}

output:

a= 2
a= 1
a= 0

b= 3
b= 3
b= 3

第一個匿名函數循環中的i 是在fmt.Println函數呼叫被推入延遲呼叫堆疊的時候估的值,因此輸出結果是2,1,0 , 第二個匿名函數中的i 是匿名函數呼叫退出階段估的值(此時i 已經變成3了),故結果輸出:3, 3,3。
其實對第二個匿名函數呼叫略加修改,就能讓它輸出和匿名函數一相同的結果:

package mainimport "fmt"func main() {
    func() {
        for i := 0; i < 3; i++ {
            defer fmt.Println("a=", i)
        }
    }()

    fmt.Println()
    func() {
        for i := 0; i < 3; i++ {
            defer func(i int) {
                fmt.Println("b=", i)
            }(i)
        }
    }()}

output:

a= 2
a= 1
a= 0

b= 2
b= 1
b= 0

恐慌(panic)和恢復(defer recover)

Go不支援異常拋出和捕獲,而是建議使用返回值明確傳回錯誤。不過,Go支援一套和異常拋出/捕獲類似的機制。此機制稱為恐慌/恢復(panic/recover)機制。

我們可以呼叫內建函數panic來產生一個恐慌以使目前協程進入恐慌狀況。

進入恐慌狀況是另一種使當前函數呼叫開始返回的途徑。一旦函數呼叫產生一個恐慌,此函數呼叫將立即進入它的退出階段,在此函數呼叫中被推入堆疊的延遲呼叫將按照它們被推入的順序逆序執行。

透過在一個延遲函數呼叫之中呼叫內建函數recover,當前協程中的一個恐慌可以被消除,從而使得當前協程重新進入正常狀況。

在一個處於恐慌狀況的協程退出之前,其中的恐慌不會蔓延到其它協程。如果一個協程在恐慌狀況下退出,它將使整個程序崩潰。看下面的兩個例子:

package mainimport (
    "fmt"
    "time")func p(a, b int) int {
    return a / b}func main() {
    go func() {
        fmt.Println(p(1, 0))
    }()
    time.Sleep(time.Second)
    fmt.Println("程序正常退出~~~")}

output:

panic: runtime error: integer pide by zero

goroutine 6 [running]:
main.p(...)
        /Users/didi/Desktop/golang/defer.go:9
main.main.func1()
        /Users/didi/Desktop/golang/defer.go:14 +0x12
created by main.main
        /Users/didi/Desktop/golang/defer.go:13 +0x39exit status 2

p函數發生panic(除數為0),因為所在協程沒有恐慌恢復機制,導致整個程式崩潰。
如果p函數所在協程加上恐慌恢復(defer recover),程式便可正常退出。

package mainimport (
    "fmt"
    "time")func p(a, b int) int {
    return a / b}func main() {
    go func() {
        defer func() {
            v := recover()
            if v != nil {
                fmt.Println("恐慌被恢复了:", v)
            }
        }()
        fmt.Println(p(1, 0))
    }()
    time.Sleep(time.Second)
    fmt.Println("程序正常退出~~~")}

output:

恐慌被恢复了: runtime error: integer pide by zero
程序正常退出~~~

更多golang相關知識,請造訪golang#更多golang相關知識,請造訪

golang######教學欄!                                    #### ##

以上是在Go中什麼是defer?怎麼用?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除