In Go werden globale Variablen in der Reihenfolge der Dateierklärungen und der Ausführung ihrer init-Funktionen initialisiert. Dies bestimmt die Korrektheit des Zugriffs auf den globalen Zustand: Wenn eine Variable in einem Paket von dem Wert einer Variablen eines anderen Pakets abhängt, dieses Paket jedoch noch nicht initialisiert wurde, tritt ein Fehler oder ein unerwartetes Verhalten auf.
Eine direkte Festlegung der "Startreihenfolge" zwischen init-Funktionen verschiedener Pakete ist nicht möglich. Der Compiler bestimmt diese Reihenfolge anhand des Importgraphen.
main.main() initialisiert.Beispiel für eine sichere verzögerte Initialisierung (lazy init):
var cfg *Config var once sync.Once func GetConfig() *Config { once.Do(func() { cfg = LoadConfigFromDisk() }) return cfg }
Frage: Können init-Funktionen verschiedener Pakete beim Start eines Go-Programms gleichzeitig (parallel) ausgeführt werden?
Richtige Antwort: Nein, init-Funktionen und globale Variablen werden strikt sequenziell in der vom Compiler festgelegten Reihenfolge ausgeführt, die bei der Analyse der Abhängigkeiten zwischen Paketen ermittelt wird. Ein paralleles Ausführen von init-Funktionen findet nicht statt.
Geschichte
In einem großen Projekt luden mehrere Module globale Konfigurationen vom Disk über init(). Ein Modul hing von einem anderen ab, aber aufgrund von Änderungen im graphen go.mod änderte sich die Initialisierungsreihenfolge — und plötzlich waren die Konfigurationswerte leer! Der Fehler trat zufällig auf und hing von der Build-Reihenfolge ab und wurde erst nach der Analyse der Abhängigkeiten und der Verlagerung der Initialisierung in eine explizite Funktion entdeckt.
Geschichte
In einem REST-Service wurde zum Cachen von Verzeichnissen eine globale Map ohne Mutex verwendet. Die Initialisierung begann aus mehreren goroutines, die in init gestartet wurden. Das Ergebnis — data race, gelegentliche Paniken, ungültige Daten in der Map. Nach dem Austausch gegen sync.Once und dem expliziten Aufruf der Initialisierung verschwand das Problem.
Geschichte
Der globale Logger wurde in der init-Funktion eines der Hilfspakete erstellt, jedoch sprach ein anderes Paket ebenfalls beim Start, bevor er initialisiert wurde, auf diesen Logger zu. Infolgedessen gingen Teile der Startprotokolle verloren, solange der Logger noch nicht existierte. Lösung — Dependency Injection und explizite Initialisierung des Loggers vor dem Start aller Dienste.