560 Stimmen

Wann wird die init() Funktion ausgeführt?

Ich habe versucht, eine genaue Erklärung dafür zu finden, was die init()-Funktion in Go macht. Ich habe gelesen, was Effective Go sagt, war mir aber unsicher, ob ich es vollständig verstanden habe. Der genaue Satz, bei dem ich unsicher bin, lautet wie folgt:

Und schließlich bedeutet schließlich: init wird aufgerufen, nachdem alle Variablendeklarationen im Paket ihre Initialisierer evaluiert haben, und diese werden erst evaluiert, nachdem alle importierten Pakete initialisiert wurden.

Was bedeutet alle Variablendeklarationen im Paket haben ihre Initialisierer evaluiert? Bedeutet es, dass die `globalen` Variablen in einem Paket und seinen Dateien deklariert werden und init() nicht ausgeführt wird, bis alles evaluiert ist und dann werden alle init-Funktionen ausgeführt und dann main(), wenn ./main_dateiname ausgeführt wird?

Ich habe auch Mark Summerfields Go-Buch gelesen, und dort steht folgendes:

Wenn ein Paket eine oder mehrere init()-Funktionen hat, werden sie automatisch ausgeführt, bevor die main()-Funktion des Hauptpakets aufgerufen wird.

Nach meinem Verständnis ist init() nur relevant, wenn du main() ausführen möchtest, oder? Jeder, der init() genauer versteht, kann mich gerne korrigieren

650voto

OneOfOne Punkte 88023

Ja, wenn du davon ausgehst, dass du dies hast:

var WhatIsThe = AnswerToLife()

func AnswerToLife() int { // 1
    return 42
}

func init() { // 2
    WhatIsThe = 0
}

func main() { // 3
    if WhatIsThe == 0 {
        fmt.Println("Alles ist eine Lüge.")
    }
}

AnswerToLife() wird garantiert ausgeführt, bevor init() aufgerufen wird, und init() wird garantiert ausgeführt, bevor main() aufgerufen wird.

Beachte, dass init() immer aufgerufen wird, unabhängig davon, ob es ein main gibt oder nicht. Wenn du also ein Paket importierst, das eine init-Funktion hat, wird diese ausgeführt.

Zusätzlich kannst du mehrere init()-Funktionen pro Paket haben; sie werden in der Reihenfolge ausgeführt, wie sie in der Datei erscheinen (nachdem alle Variablen natürlich initialisiert wurden). Wenn sie sich über mehrere Dateien erstrecken, werden sie in lexikalischer Dateinamenreihenfolge ausgeführt (wie von @benc hervorgehoben):

Es scheint, dass init()-Funktionen in lexikalischer Dateinamenreihenfolge ausgeführt werden. Die Go-Spezifikation besagt, dass "Build-Systeme ermutigt werden, mehrere Dateien, die zu einem Paket gehören, in lexikalischer Dateinamenreihenfolge an einen Compiler zu übergeben". Es scheint, dass go build auf diese Weise funktioniert.


Viele interne Go-Pakete verwenden init(), um Tabellen und ähnliches zu initialisieren, zum Beispiel https://github.com/golang/go/blob/883bc6/src/compress/bzip2/bzip2.go#L480

509voto

weaming Punkte 4377

Sieh dir dieses Bild an. :)

import --> const --> var --> init()

  1. Wenn ein Paket andere Pakete importiert, werden die importierten Pakete zuerst initialisiert.

  2. Die Konstanten des aktuellen Pakets werden dann initialisiert.

  3. Die Variablen des aktuellen Pakets werden dann initialisiert.

  4. Zuletzt wird die init()-Funktion des aktuellen Pakets aufgerufen.

Ein Paket kann mehrere init-Funktionen haben (entweder in einer einzigen Datei oder verteilt auf mehrere Dateien), und sie werden in der Reihenfolge aufgerufen, in der sie dem Compiler präsentiert wurden.

Ein Paket wird nur einmal initialisiert, auch wenn es von mehreren Paketen importiert wird.

30voto

rifflock Punkte 431

Etwas, das hinzugefügt werden soll (das ich als Kommentar hinzugefügt hätte, aber zum Zeitpunkt des Schreibens dieses Beitrags hatte ich noch nicht genug Reputation)

Wenn mehrere Inits im selben Paket verwendet werden, habe ich bisher keine garantierte Möglichkeit gefunden zu wissen, in welcher Reihenfolge sie ausgeführt werden. Zum Beispiel habe ich:

Paket Konfiguration
    - config.go
    - router.go

Sowohl config.go als auch router.go enthalten init()-Funktionen, aber bei der Ausführung wurde zuerst die Funktion in router.go ausgeführt (was meine App zum Absturz brachte).

Wenn Sie in der Situation sind, dass Sie mehrere Dateien haben, von denen jede ihre eigene init()-Funktion hat, sollten Sie sehr darauf achten, dass Sie nicht garantieren können, dass eine vor der anderen aufgerufen wird. Es ist besser, eine Variablendeklaration zu verwenden, wie es OneToOne in seinem Beispiel zeigt. Der beste Teil ist: Diese Variablendeklaration wird vor ALLEN init()-Funktionen im Paket ausgeführt.

Zum Beispiel

config.go:

var ConfigSuccess = configureApplication()

func init() {
    doSomething()
}

func configureApplication() bool {
    l4g.Info("Applikation wird konfiguriert...")
    if valid := loadCommandLineFlags(); !valid {
        l4g.Critical("Konnte die Befehlszeilenoptionen nicht laden")
        return false
    }
    return true
}

router.go:

func init() {
    var (
        rwd string
        tmp string
        ok  bool
    )
    if metapath, ok := Config["fs"]["metapath"].(string); ok {
        var err error
        Conn, err = services.NewConnection(metapath + "/metadata.db")
        if err != nil {
            panic(err)
        }
    }
}

Unabhängig davon, ob var ConfigSuccess = configureApplication() in router.go oder config.go existiert, wird es ausgeführt, bevor EINE der init()-Funktionen ausgeführt wird.

19voto

Alok Kumar Singh Punkte 2321

Hier ist ein weiteres Beispiel - https://play.golang.org/p/9P-LmSkUMKY

package main

import (
    "fmt"
)

func callOut() int {
    fmt.Println("Außerhalb wird ausgeführt")
    return 1
}

var test = callOut()

func init() {
    fmt.Println("Init3 wird ausgeführt")
}

func init() {
    fmt.Println("Init wird ausgeführt")
}

func init() {
    fmt.Println("Init2 wird ausgeführt")
}

func main() {
    fmt.Println("Mach dein Ding!")
}

Ausgabe des obigen Programms

$ go run init/init.go
Außerhalb wird ausgeführt
Init3 wird ausgeführt
Init wird ausgeführt
Init2 wird ausgeführt
Mach dein Ding!

14voto

VonC Punkte 1117238

Wann wird die init()-Funktion ausgeführt?

Ab Go 1.16 (Q1 2021) können Sie genau sehen, wann sie ausgeführt wird und wie lange.

Siehe Commit 7c58ef7 von CL (Change List) 254659, der Problem 41378 behebt.

Laufzeitumgebung: Implementierung der Unterstützung für GODEBUG=inittrace=1

Das Setzen von inittrace=1 bewirkt, dass die Laufzeitumgebung eine einzige Zeile auf den Standardfehler ausgibt, für jedes Paket mit init-Arbeit, die Ausführungszeit und Speicherzuweisung zusammenfassend.

Die ausgegebenen Debug-Informationen für init-Funktionen können verwendet werden, um Engpässe oder Regressionen in der Startleistung von Go zu finden.

Pakete ohne init-Arbeit (benutzerdefiniert oder vom Compiler generiert) werden ausgelassen.

Das Verfolgen von Plugin-Inits wird nicht unterstützt, da sie gleichzeitig ausgeführt werden können. Dies würde die Implementierung des Tracing komplexer machen, während Unterstützung für einen sehr seltenen Anwendungsfall hinzugefügt wird. Plugin-Inits können separat verfolgt werden, indem ein Hauptpaket getestet wird, das die Plugin-Pakete explizit importiert.

$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...

Inspiriert von stapelberg@google.com, der doInit in einem Prototyp instrumentiert hat, um init-Zeiten mit GDB zu messen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X