Programmation asynchrone en Go : guide complet pour débutants

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Programmation asynchrone en Go : guide complet pour débutants

Go (Golang) a été créé en tenant compte des besoins du développement moderne, où la performance et le parallélisme jouent un rôle clé. Contrairement à de nombreux langages où l'asynchronisme est implémenté via des callbacks ou des promesses, Go propose des mécanismes intégrés et élégants : les goroutines et les canaux. Ce guide vous présentera les bases de la programmation asynchrone en Go et vous montrera comment écrire du code concurrent efficace.



Que sont les goroutines et en quoi diffèrent-elles des threads ?

Une goroutine est un fil d'exécution léger géré par le runtime de Go. Contrairement aux threads système du système d'exploitation, les goroutines consomment beaucoup moins de ressources : leur pile commence à quelques kilo-octets et peut croître et diminuer dynamiquement. Cela permet d'exécuter des millions de goroutines simultanément sans charge mémoire significative.

Lancer une goroutine est très simple : il suffit d'ajouter le mot-clé go avant l'appel d'une fonction :

package main

import ( "fmt" "time")

func sayHello() { fmt.Println("Bonjour depuis une goroutine !")}

func main() { go sayHello() // Lancement de la goroutine time.Sleep(100 * time.Millisecond) // On attend que la goroutine ait le temps de s'exécuter fmt.Println("Fonction principale terminée")}

Il est important de noter : si la fonction principale main() se termine, toutes les goroutines lancées seront arrêtées de force. Pour la synchronisation, nous utilisons des outils spéciaux.



Synchronisation avec WaitGroup

La façon la plus simple d'attendre la fin des goroutines est d'utiliser sync.WaitGroup. Il fonctionne comme un compteur : on l'incrémente au lancement d'une goroutine et on le décrémente à la fin. La fonction principale se bloque jusqu'à ce que le compteur atteigne zéro.

package main

import ( "fmt" "sync" "time")

func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // Décrémente le compteur à la fin fmt.Printf("Travailleur %d a commencé le travail", id) time.Sleep(time.Second) fmt.Printf("Travailleur %d a terminé le travail", id)}

func main() { var wg sync.WaitGroup

for i := 1; i <= 3; i++ { wg.Add(1) // Incrémente le compteur avant le lancement go worker(i, &wg) }

wg.Wait() // Attend la fin de toutes les goroutines fmt.Println("Tous les travailleurs ont terminé le travail")}

Remarquez : wg.Add(1) est appelé avant le lancement de la goroutine, et wg.Done() à l'intérieur (généralement via defer). Cela garantit que le compteur sera correctement décrémenté même en cas de panique.



Canaux : communication entre goroutines

Les canaux (channels) sont des « tuyaux » typés par lesquels les goroutines peuvent échanger des données en toute sécurité. Les canaux assurent la synchronisation par défaut : l'envoi dans un canal bloque la goroutine jusqu'à ce qu'une autre goroutine lise la valeur, et vice versa.

Les canaux sont créés avec la fonction make :

package main

import "fmt"

func main() { // Crée un canal non bufferisé pour les entiers ch := make(chan int)

// Lance une goroutine qui envoie des données go func() { ch <- 42 // Envoie la valeur dans le canal }()

// Lit depuis le canal (bloque jusqu'à réception d'une valeur) value := <-ch fmt.Println("Valeur reçue :", value)}

Il existe deux types de canaux :

  • Non bufferisés — synchrones, l'envoi bloque jusqu'à la réception.
  • Bufferisés — asynchrones, l'envoi bloque uniquement lorsque le tampon est plein.
// Création d'un canal bufferisé de 3 élémentsbufferedCh := make(chan string, 3)bufferedCh <- "A"bufferedCh <- "B"bufferedCh <- "C" // Ne bloque pas tant qu'il n'est pas plein


Blogs

Book Recommendations