Heim >Backend-Entwicklung >Golang >Was ist „Defer' in Go? Wie benutzt man es?

Was ist „Defer' in Go? Wie benutzt man es?

藏色散人
藏色散人nach vorne
2021-09-01 15:52:432573Durchsuche

Dieser Artikel wird Ihnen durch die Go-SpracheTutorial-Kolumne vorgestellt. Das Thema dreht sich um das Erlernen und die Verwendung von Go Defer. Ich hoffe, dass es für Freunde in Not hilfreich sein wird.

In Go kann ein Funktionsaufruf einem Schlüsselwort defer folgen, um einen verzögerten Funktionsaufruf zu bilden. Wenn ein Funktionsaufruf verzögert wird, wird er nicht sofort ausgeführt. Es wird in einen verzögerten Aufrufstapel verschoben, der von der aktuellen Coroutine verwaltet wird. Wenn ein Funktionsaufruf (der ein verzögerter Aufruf sein kann oder nicht) zurückkehrt und in seine Exit-Phase eintritt, werden alle verzögerten Aufrufe, die innerhalb dieses Funktionsaufrufs gepusht wurden, in umgekehrter Reihenfolge der Reihenfolge ausgeführt, in der sie auf den Stapel geschoben wurden . Wenn alle diese verzögerten Aufrufe ausgeführt werden, wird der Funktionsaufruf tatsächlich beendet. Ein einfaches Beispiel:

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)}
Ausgabe:

参数a=1,参数b=2,两数之和为3
sum函数finished
sum函数即将返回
defer关键字后面,形成一个延迟函数调用。
当一个函数调用被延迟后,它不会立即被执行。它将被推入由当前协程维护的一个延迟调用堆栈。 当一个函数调用(可能是也可能不是一个延迟调用)返回并进入它的退出阶段后,所有在此函数调用中已经被推入的延迟调用将被按照它们被推入堆栈的顺序逆序执行。 当所有这些延迟调用执行完毕后,此函数调用也就真正退出了。
举个简单的例子:
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函数参数估值

  • 对于一个延迟函数调用,它的实参是在此调用被推入延迟调用堆栈的时候被估值的。
  • 一个匿名函数体内的表达式是在此函数被执行的时候才会被逐个估值的,不管此函数是被普通调用还是延迟调用。
    例子1:
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

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(i int) {
                fmt.Println("b=", i)
            }(i)
        }
    }()}

output:

a= 2
a= 1
a= 0

b= 2
b= 1
b= 0

第一个匿名函数循环中的 i 是在 fmt.Println函数调用被推入延迟调用堆栈的时候估的值,因此输出结果是 2,1,0 , 第二个匿名函数中的 i 是匿名函数调用退出阶段估的值(此时 i 已经变成3了),故结果输出:3,3,3。
其实对第二个匿名函数调用略加修改,就能使它输出和匿名函数一相同的结果:

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

恐慌(panic)和恢复(defer + recover)

Go不支持异常抛出和捕获,而是推荐使用返回值显式返回错误。 不过,Go支持一套和异常抛出/捕获类似的机制。此机制称为恐慌/恢复(panic/recover)机制。

我们可以调用内置函数panic来产生一个恐慌以使当前协程进入恐慌状况。

进入恐慌状况是另一种使当前函数调用开始返回的途径。 一旦一个函数调用产生一个恐慌,此函数调用将立即进入它的退出阶段,在此函数调用中被推入堆栈的延迟调用将按照它们被推入的顺序逆序执行。

通过在一个延迟函数调用之中调用内置函数recoverTatsächlich verwaltet jede Coroutine zwei Aufrufstapel.

  • Einer ist der normale Funktionsaufrufstapel. In diesem Stapel besteht eine Aufrufbeziehung zwischen zwei benachbarten Aufrufen. Anrufe, die spät auf dem Stapel platziert werden, werden von Anrufen aufgerufen, die früh auf dem Stapel platziert werden. Der früheste in diesem Stapel abgelegte Aufruf ist der Startaufruf der entsprechenden Coroutine.
  • Ein weiterer Stapel ist der oben erwähnte verzögerte Aufrufstapel. Es gibt keine Aufrufbeziehung zwischen zwei beliebigen Aufrufen im verzögerten Aufrufstapel.

defer-Funktionsparameterauswertung


  • Bei einem verzögerten Funktionsaufruf werden seine tatsächlichen Parameter ausgewertet, wenn der Aufruf in den verzögerten Aufrufstapel von verschoben wird.
  • Die Ausdrücke im Hauptteil einer anonymen Funktion werden einzeln ausgewertet, wenn die Funktion ausgeführt wird, unabhängig davon, ob die Funktion normal oder verzögert aufgerufen wird. Beispiel 1:
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("程序正常退出~~~")}

Ausgabe:

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

defer Print(a) wird zum verzögerten Aufrufstapel hinzugefügt, der Wert von a ist 5, also ist das Ausgabeergebnis von defer Print(a). 5 Beispiel 2:rrreeeAusgabe:rrreeei in der ersten anonymen Funktionsschleife ist der geschätzte Wert, wenn der Funktionsaufruf fmt.Println in den verzögerten Aufrufstapel verschoben wird, sodass das Ausgabeergebnis 2, 1, 0 ist. i in der zweiten anonymen Funktion ist der Wert, der während der Exit-Phase des anonymen Funktionsaufrufs geschätzt wurde (i ist zu diesem Zeitpunkt 3 geworden), daher lautet die Ergebnisausgabe: 3, 3, 3. Tatsächlich kann er mit einer geringfügigen Änderung am zweiten anonymen Funktionsaufruf das gleiche Ergebnis wie der anonyme Funktionsaufruf ausgeben:
rrreee

output:🎜rrreee🎜🎜🎜🎜Panik und Wiederherstellung (aufschieben + wiederherstellen)🎜 🎜🎜Los unterstützt das Auslösen und Abfangen von Ausnahmen nicht, es wird jedoch empfohlen, Rückgabewerte zu verwenden, um Fehler explizit zurückzugeben. Go unterstützt jedoch einen ähnlichen Mechanismus wie das Auslösen/Abfangen von Ausnahmen. Dieser Mechanismus wird als Panik-/Wiederherstellungsmechanismus bezeichnet. 🎜🎜Wir können die integrierte Funktion panic aufrufen, um eine Panik zu erzeugen und die aktuelle Coroutine in einen Panikzustand zu versetzen. 🎜🎜Das Eintreten in einen Panikzustand ist eine weitere Möglichkeit für den aktuellen Funktionsaufruf, zurückzukehren. Sobald ein Funktionsaufruf eine Panik auslöst, tritt der Funktionsaufruf sofort in seine Beendigungsphase ein und die zurückgestellten Aufrufe, die innerhalb des Funktionsaufrufs auf den Stapel verschoben werden, werden in umgekehrter Reihenfolge der Reihenfolge ausgeführt, in der sie übertragen wurden. 🎜🎜Durch den Aufruf der integrierten Funktion recover in einem verzögerten Funktionsaufruf kann eine Panik in der aktuellen Coroutine beseitigt werden, sodass die aktuelle Coroutine wieder in den Normalzustand zurückkehren kann. 🎜🎜Bevor eine Coroutine in Panik aussteigt, breitet sich die Panik nicht auf andere Coroutinen aus. Wenn eine Coroutine in einem Panikzustand beendet wird, stürzt das gesamte Programm ab. Schauen Sie sich die folgenden zwei Beispiele an: 🎜rrreee🎜output:🎜rrreee🎜p-Funktion gerät in Panik (Divisor ist 0), da die Coroutine keinen Panik-Wiederherstellungsmechanismus hat, was zum Absturz des gesamten Programms führt. 🎜Wenn die Coroutine, in der sich die p-Funktion befindet, mit Panikwiederherstellung (Verzögern + Wiederherstellen) hinzugefügt wird, kann das Programm normal beendet werden. 🎜rrreee🎜Ausgabe:🎜rrreee🎜🎜Für mehr Golang-bezogenes Wissen besuchen Sie bitte die 🎜🎜Golang🎜🎜Tutorial-Kolumne 🎜

Das obige ist der detaillierte Inhalt vonWas ist „Defer' in Go? Wie benutzt man es?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen