ProgrammationDéveloppeur Go Senior

Quelles sont les caractéristiques du travail avec des variables globales en Go, comment éviter leur mauvaise initialisation et la concurrence au moment de l'exécution (ordre d'initialisation) ? Quelles sont les pièges typiques ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Go, les variables globales sont initialisées dans l'ordre de déclaration des fichiers et de la séquence de leurs fonctions init. Cela dépend de la correction de l'accès à l'état global : si une variable dans un paquet dépend de la valeur d'une variable d'un autre paquet, mais que ce paquet n'a pas encore été initialisé, cela provoquera une erreur ou un comportement non évident.

Il n'est pas possible de définir directement un "ordre de démarrage" entre les fonctions init de différents paquets. Le compilateur détermine cet ordre en fonction du graphique d'importation.

  • Toutes les variables globales sont initialisées avant l'appel de la fonction main.main().
  • Il n'est pas recommandé de faire une initialisation d'état avec des dépendances excessives lors de l'init du paquet !
  • Pour la sécurité des threads des variables globales, on utilise sync.Once ou des mutex lors d'une initialisation complexe.

Exemple d'initialisation différée sécurisée (lazy init):

var cfg *Config var once sync.Once func GetConfig() *Config { once.Do(func() { cfg = LoadConfigFromDisk() }) return cfg }

Question piégée.

Question : Les fonctions init de différents paquets peuvent-elles s'exécuter simultanément (parallèlement) lors du démarrage d'un programme en Go ?

Réponse correcte : Non, les fonctions init et les variables globales s'exécutent strictement de manière séquentielle dans l'ordre défini par le compilateur lors de l'analyse des dépendances entre les paquets. Il n'y a pas d'exécution parallèle des fonctions init.


Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

Dans un grand projet, plusieurs modules chargeaient des configurations globales à partir du disque via init(). Un module dépendait d'un autre, mais en raison de la restructuration du graphique go.mod, l'ordre d'initialisation a changé - et soudain, les valeurs de configuration sont devenues vides ! L'erreur apparaissait de manière aléatoire, dépendant de l'ordre de construction, et n'a été trouvée qu'après l'analyse des dépendances et le transfert de l'initialisation dans une fonction explicite.


Histoire

Dans un service REST, un map global était utilisé pour la mise en cache de répertoires sans mutex. L'initialisation était lancée par plusieurs goroutines démarrant dans init. En conséquence - data race, paniques périodiques, données non valides dans le map. Après remplacement par sync.Once et appel explicite de l'initialisation, le problème a disparu.


Histoire

Un logger global était créé dans la fonction init d'un des paquets auxiliaires, mais un autre paquet accédait également à ce logger au démarrage, avant son initialisation. En fin de compte, une partie des journaux de démarrage était perdue tant que le logger n'existait pas encore. La solution - injection de dépendances et initialisation explicite du logger avant le démarrage de tous les services.