Ablaufsteuerung mit den Funktionen defer, panic und recover
Sehen wir uns nun einige Ablaufsteuerungen an, die spezifisch für Go sind: defer
, panic
und recover
. Jede dieser Funktionen verfügt über mehrere Anwendungsfälle. Hier untersuchen wir die wichtigsten Anwendungsfälle.
Defer-Funktion
In Go stellt eine defer
-Anweisung die Ausführung einer Funktion (einschließlich aller Parameter) zurück, bis die Funktion abgeschlossen ist, die die defer
-Anweisung enthält. Im Allgemeinen stellen Sie eine Funktion zurück, wenn Sie vermeiden möchten, dass Aufgaben wie das Schließen einer Datei oder das Ausführen eines Bereinigungsvorgangs vergessen werden.
Sie können beliebig viele Funktionen zurückstellen. Die defer
-Anweisungen werden in umgekehrter Reihenfolge ausgeführt, von der letzten bis zur ersten Anweisung.
Überprüfen Sie, wie dieses Muster funktioniert, indem Sie den folgenden Beispielcode ausführen:
package main
import "fmt"
func main() {
for i := 1; i <= 4; i++ {
defer fmt.Println("deferred", -i)
fmt.Println("regular", i)
}
}
Hier ist die Codeausgabe:
regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1
Beachten Sie in diesem Beispiel, dass bei jeder Verzögerung von fmt.Println("deferred", -i)
der Wert für i
gespeichert und der Funktionsaufruf einer Warteschlange hinzugefügt wurde. Nachdem die main()
-Funktion die Ausgabe der regular
-Werte abgeschlossen hat, wurden alle zurückgestellten Aufrufe ausgeführt. Beachten Sie, dass die Ausgabe der verzögerten Aufrufe in umgekehrter Reihenfolge (LIFO, „last in, first out“) erfolgt, wenn sie aus der Warteschlange abgearbeitet werden.
Ein typischer Anwendungsfall für die defer
-Funktion ist das Schließen einer Datei, nachdem Sie die Arbeit daran abgeschlossen haben. Hier sehen Sie ein Beispiel:
package main
import (
"io"
"os"
"fmt"
)
func main() {
newfile, error := os.Create("learnGo.txt")
if error != nil {
fmt.Println("Error: Could not create file.")
return
}
defer newfile.Close()
if _, error = io.WriteString(newfile, "Learning Go!"); error != nil {
fmt.Println("Error: Could not write to file.")
return
}
newfile.Sync()
}
Nachdem Sie eine Datei erstellt oder geöffnet haben, stellen Sie die .Close()
-Funktion zurück, damit Sie das Schließen der Datei nicht vergessen, wenn Sie fertig sind.
Panic-Funktion
Laufzeitfehler bewirken bei Go-Programmen eine Panik, etwa beim Versuch, durch einen Index außerhalb der Grenzen auf ein Array zuzugreifen oder einen Nullzeiger zu dereferenzieren. Sie können die Panik eines Programms auch erzwingen.
Die integrierte Funktion panic()
beendet den normalen Ablauf der Steuerung in einem Go-Programm. Wenn Sie einen panic
-Aufruf verwenden, werden alle zurückgestellten (deferred) Funktionsaufrufe normal ausgeführt. Der Prozess arbeitet den Stapel von unten nach oben ab, bis alle Funktionen zurückkehren. Das Programm stürzt dann mit einer Protokollnachricht ab. Die Nachricht enthält alle Fehlerinformationen und eine Stapelüberwachung, die Ihnen bei der Diagnose der Grundursache des Problems hilft.
Wenn Sie die panic()
-Funktion aufrufen, können Sie einen beliebigen Wert als Argument hinzufügen. Normalerweise senden Sie eine Fehlermeldung, warum die Panik aufgetreten ist.
Beispielsweise sind im folgenden Code die Funktionen panic
und defer
kombiniert. Versuchen Sie, diesen Code auszuführen, um zu sehen, wie die Ablaufsteuerung unterbrochen wird. Beachten Sie, dass die Bereinigungsprozesse weiterhin ausgeführt werden.
package main
import "fmt"
func highlow(high int, low int) {
if high < low {
fmt.Println("Panic!")
panic("highlow() low greater than high")
}
defer fmt.Println("Deferred: highlow(", high, ",", low, ")")
fmt.Println("Call: highlow(", high, ",", low, ")")
highlow(high, low + 1)
}
func main() {
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
So sieht die Ausgabe aus:
Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
panic: highlow() low greater than high
goroutine 1 [running]:
main.highlow(0x2, 0x3)
/tmp/sandbox/prog.go:13 +0x34c
main.highlow(0x2, 0x2)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x1)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x0)
/tmp/sandbox/prog.go:18 +0x298
main.main()
/tmp/sandbox/prog.go:6 +0x37
Program exited: status 2.
Folgendes passiert, wenn der Code ausgeführt wird:
Alles wird normal ausgeführt. Das Programm gibt die der Funktion
highlow()
übergebenen hohen und niedrigen Werte aus.Wenn der Wert von
low
größer als der Wert vonhigh
ist, löst das Programm den Panikmodus aus. Die NachrichtPanic!
wird angezeigt. An diesem Punkt wird die Ablaufsteuerung unterbrochen, und alle zurückgestellten Funktionen beginnen mit der Ausgabe der NachrichtDeferred...
.Das Programm stürzt ab, und Sie sehen die vollständige Stapelüberwachung. Die Nachricht
Program finished successfully!
wird nicht angezeigt.
Ein Aufruf von panic()
wird normalerweise ausgeführt, wenn unerwartet schwerwiegende Fehler auftreten. Zum Vermeiden eines Programmabsturzes können Sie eine weitere Funktion namens recover()
verwenden.
Recover-Funktion
Gelegentlich möchten Sie vielleicht einen Programmabsturz vermeiden und stattdessen den Fehler intern melden. Oder vielleicht möchten Sie das Problem beheben, bevor Sie das Programm abstürzen lassen. Sie können z. B. jede Verbindung mit einer Ressource schließen, um weitere Probleme zu vermeiden.
Go stellt die integrierte Funktion recover()
bereit, mit der Sie nach einer Panik die Kontrolle wiedererlangen können. Sie rufen recover
nur in einer Funktion auf, in der Sie auch defer
aufrufen. Wenn Sie die Funktion recover()
aufrufen, gibt sie nil
zurück und hat bei der normalen Ausführung keine weiteren Auswirkungen.
Versuchen Sie, die main
-Funktion im vorstehenden Code zu ändern, um einen Aufruf der recover()
-Funktion hinzuzufügen. Beispiel:
func main() {
defer func() {
handler := recover()
if handler != nil {
fmt.Println("main(): recover", handler)
}
}()
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
Wenn Sie das Programm ausführen, sollte die Ausgabe wie folgt aussehen:
Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
main(): recover from panic highlow() low greater than high
Program exited.
Sehen Sie den Unterschied zur vorherigen Version? Der Hauptunterschied ist, dass Sie der Fehler der Stapelüberwachung nicht mehr angezeigt wird.
In der Funktion main()
stellen Sie eine anonyme Funktion zurück, in der Sie die Funktion recover()
aufrufen. Beim Aufruf von recover()
kann nil
nicht zurückgegeben werden, wenn im Programm der Panik-Modus auftritt. Sie können hier etwas unternehmen, um das Durcheinander zu bereinigen, aber in diesem Fall erfolgt einfach eine Ausgabe.
Die Kombination der Funktionen panic
und recover
ist die idiomatische Art und Weise, wie Go mit Ausnahmen umgeht. In anderen Programmiersprachen wird der try/catch
-Block verwendet. Go bevorzugt den Ansatz, den Sie hier untersucht haben.
Weitere Informationen finden Sie unter dem Vorschlag zum Hinzufügen einer integrierten try
-Funktion in Go.