Wie beende ich ein Go-Programm, um zurückgestellte Anrufe zu berücksichtigen?

76

Ich muss verwenden defer, um Zuordnungen freizugeben, die manuell mit der CBibliothek erstellt wurden, aber ich muss auch os.Exitirgendwann den Status ungleich 0 haben. Der schwierige Teil ist, dass os.Exitjede zurückgestellte Anweisung übersprungen wird:

package main

import "fmt"
import "os"

func main() {

    // `defer`s will _not_ be run when using `os.Exit`, so
    // this `fmt.Println` will never be called.
    defer fmt.Println("!")
    // sometimes ones might use defer to do critical operations
    // like close a database, remove a lock or free memory

    // Exit with status code.
    os.Exit(3)
}

Spielplatz: http://play.golang.org/p/CDiAh9SXRM gestohlen von https://gobyexample.com/exit

Wie kann man ein Go-Programm beenden, das deklarierte deferAnrufe berücksichtigt? Gibt es eine Alternative zu os.Exit?

Marcio
quelle
@ctcherry kein Erfolg mit defer os.Exit: play.golang.org/p/IsSI9VB7j8
marcio
Oh, ich verstehe, das ist ein Problem. In meinem Fall kann ich nur aufschieben, os.Exitnachdem ich andere Operationen ausgeführt habe, die ebenfalls etwas aufschieben ... lassen Sie mich etwas mehr darüber nachdenken.
Marcio
deferin mainKombination mit os.Exitist definitiv eine raue Stelle. Eine Architektur wie @Rob Napier ist ein besserer Weg.
Ctcherry

Antworten:

28

runtime.Goexit() ist der einfache Weg, dies zu erreichen.

Goexit beendet die Goroutine, die es aufruft. Keine andere Goroutine ist betroffen. Goexit führt alle zurückgestellten Anrufe aus, bevor die Goroutine beendet wird. Da Goexit jedoch nicht in Panik gerät, geben alle Wiederherstellungsaufrufe in diesen verzögerten Funktionen null zurück.

Jedoch:

Wenn Sie Goexit von der Haupt-Goroutine aus aufrufen, wird diese Goroutine beendet, ohne dass die Haupt-Goroutine zurückkehrt. Da func main nicht zurückgekehrt ist, setzt das Programm die Ausführung anderer Goroutinen fort. Wenn alle anderen Goroutinen beendet werden, stürzt das Programm ab.

Wenn Sie es also von der Hauptgoroutine aus aufrufen, mainmüssen Sie oben hinzufügen

defer os.Exit(0)

Darunter möchten Sie möglicherweise einige andere deferAnweisungen hinzufügen , die die anderen Goroutinen anweisen, anzuhalten und aufzuräumen.

EMBLEM
quelle
1
Ich hatte keine Ahnung, dass es runtime.Goexit()existiert. Ist das aus einer neueren Version?
Marcio
2
@marcio Ich habe ein bisschen in den Go-Repositories gegraben. Ich konnte nicht genau finden, wann es eingeführt wurde, aber ich habe diesen Test gefunden, der darauf verweist und "Copyright 2013" ist, bevor Sie diese Frage gestellt haben.
EMBLEM
2
@marcio Ich habe ein bisschen mehr gegraben und ein Archiv der Dokumentation von 2009 gefunden . Goexit ist dort aufgeführt.
EMBLEM
Ich werde dies als akzeptierte Antwort markieren. Die anderen Antworten sind definitiv noch gültig, aber dies scheint der einfachste Ansatz zu sein
vorerst
42

Bewegen Sie Ihr Programm einfach um eine Ebene nach unten und geben Sie Ihren Exit-Code zurück:

package main

import "fmt"
import "os"

func doTheStuff() int {
    defer fmt.Println("!")

    return 3
}

func main() {
    os.Exit(doTheStuff())
}
Rob Napier
quelle
Also sollte ich nicht deferinside func mainoder eine Funktion verwenden, die beendet wird?
Marcio
4
Genauer gesagt, ich empfehle nicht, os.Exit()an zufälligen Stellen im Code zu verwenden. Dies macht das Testen neben dem Problem der Fehlercodes sehr schwierig. Die Lösung von peterSO, die @ctcherry verlinkt, ist in Ordnung, lässt sich aber IMO nicht gut auf ein größeres Programm skalieren. Sie müssten das codeglobale machen. Ich glaube, Sie sollten main () ziemlich einfach halten und sich nur um die Dinge auf Betriebssystemebene kümmern (wie den endgültigen Statuscode).
Rob Napier
Hallo, ich habe einen Weg gefunden, damit umzugehen, ohne eine Architektur aufzuerlegen. Wie auch immer, +1, weil es ein guter Rat ist, immer zuerst KISS zu probieren.
Marcio
28

Nach einigen Recherchen auf diese verweisen dies , fand ich eine Alternative , dass:

Wir können die Vorteile der panicund recover. Es stellt sich heraus, dass Anrufe panicvon Natur aus deferhonoriert werden, aber auch immer mit einem Nicht- 0Statuscode beendet werden und eine Stapelverfolgung ausgegeben wird. Der Trick ist, dass wir den letzten Aspekt des Panikverhaltens überschreiben können mit:

package main

import "fmt"
import "os"

type Exit struct{ Code int }

// exit code handler
func handleExit() {
    if e := recover(); e != nil {
        if exit, ok := e.(Exit); ok == true {
            os.Exit(exit.Code)
        }
        panic(e) // not an Exit, bubble up
    }
}

Um ein Programm zu einem beliebigen Zeitpunkt deferzu beenden und dennoch alle deklarierten Anweisungen beizubehalten, müssen Sie nur einen ExitTyp ausgeben :

func main() {
    defer handleExit() // plug the exit handler
    defer fmt.Println("cleaning...")
    panic(Exit{3}) // 3 is the exit code
}

Es ist kein Refactoring erforderlich, außer dass eine Leitung eingesteckt wird func main:

func main() {
    defer handleExit()
    // ready to go
}

Dies lässt sich mit größeren Codebasen recht gut skalieren, sodass ich es zur Überprüfung zur Verfügung stellen werde. Ich hoffe es hilft.

Spielplatz: http://play.golang.org/p/4tyWwhcX0-

Marcio
quelle
Beste Antwort von allen bisher! Eine Frage, wie man dann mit dem normalen Ausstieg umgeht? Wie kann man sicherstellen, dass der handleExitAnruf auch bei normalem Ausgang getätigt wird? Ref: play.golang.org/p/QDJum4kOXk entfernen Sie den Kommentar //panicund sehen Sie den Unterschied.
xpt
Ich denke, ein Handle für ein normales Exit 0-Ereignis existiert nicht, aber Sie können immer in Panik geraten (Exit {0}) und es dann behandeln oder handleNormalExit () vor irgendetwas anderem auf main () verschieben?
Marcio
16

Für die Nachwelt war dies für mich eine elegantere Lösung:

func main() { 
    retcode := 0
    defer func() { os.Exit(retcode) }()
    defer defer1()
    defer defer2()

    [...]

    if err != nil {
        retcode = 1
        return
    }
}
Mike Gleason jr. Couturier
quelle
1
Dies nimmt wirklich die besten Teile der anderen Antworten.
PRMan
0

Ich würde vorschlagen, eine Goroutine zu verwenden

func main(){
     fmt.Println("Hello world")
     go func(){
          os.Exit(0)
     }
}
Redfer
quelle