Aller au contenu
Développement medium

Goroutines et channels en Go

8 min de lecture

Go

Une goroutine est une fonction qui s'exécute en parallèle, pour un coût dérisoire ; les channels leur permettent de communiquer sans partager de mémoire. C'est la marque de fabrique de Go. Ce guide vous apprend à lancer des goroutines, à les faire coopérer par channels, à construire un pool de workers et à éviter les deux pièges classiques : le deadlock et la fuite de goroutine. Public visé : vous connaissez les fonctions et les slices. Exemples testés sur Go 1.26.

  • Lancer une goroutine avec go et communiquer par channel.
  • Faire coopérer plusieurs workers sur un même channel.
  • Fermer un channel et détecter la fermeture avec range.
  • Éviter deadlocks et fuites de goroutines.

Le mot-clé go devant un appel de fonction l'exécute dans une goroutine, sans bloquer l'appelant. Une goroutine est bien plus légère qu'un thread système : on peut en lancer des milliers.

go faireQuelqueChose() // part en parallèle, main continue

Le piège immédiat : si main se termine, le programme s'arrête, goroutines en cours comprises. Il faut donc un moyen de coordonner la fin. C'est là qu'interviennent les channels et sync.WaitGroup.

Un channel est un tuyau typé par lequel les goroutines s'échangent des valeurs. La philosophie Go : « ne communiquez pas en partageant la mémoire, partagez la mémoire en communiquant ». On envoie avec ch <- v et on reçoit avec v := <-ch.

ch := make(chan int)
go func() { ch <- 42 }() // envoie dans une goroutine
v := <-ch // reçoit (bloque jusqu'à réception)
fmt.Println(v) // 42

Un channel non bufferisé synchronise : l'envoi bloque jusqu'à ce qu'une réception ait lieu, et inversement. C'est ce rendez-vous qui coordonne les goroutines sans verrou explicite.

Le motif le plus utile en pratique : distribuer des tâches à plusieurs workers qui les traitent en parallèle. On combine un channel de jobs, un channel de résultats et un sync.WaitGroup pour attendre la fin.

func main() {
jobs := make(chan int, 5)
resultats := make(chan int, 5)
var wg sync.WaitGroup
for w := 1; w <= 3; w++ { // 3 workers concurrents
wg.Add(1)
go func() {
defer wg.Done()
for j := range jobs { // consomme jusqu'à fermeture
resultats <- j * j
}
}()
}
for i := 1; i <= 5; i++ {
jobs <- i
}
close(jobs) // signale la fin aux workers
wg.Wait() // attend tous les workers
close(resultats) // puis ferme les résultats
total := 0
for r := range resultats {
total += r
}
fmt.Println(total) // 55
}

Sortie :

55

Trois workers se partagent les cinq jobs. close(jobs) signale qu'il n'y aura plus de tâches : le for range de chaque worker se termine alors proprement. On attend tous les workers avec wg.Wait() avant de fermer les résultats, sinon on lirait un channel encore en cours d'écriture.

Fermer un channel avec close indique qu'aucune valeur ne viendra plus. Un for v := range ch s'arrête automatiquement à la fermeture ; la réception v, ok := <-ch met ok à false. Deux règles d'or :

  • Seul l'émetteur ferme un channel, jamais le récepteur.
  • Ne jamais écrire dans un channel fermé (panique immédiate).
  • Deadlock : toutes les goroutines sont bloquées en attente mutuelle. Go le détecte souvent au runtime (all goroutines are asleep - deadlock!). Cause classique : lire un channel que personne ne remplit, ou oublier de le fermer.
  • Fuite de goroutine : une goroutine reste bloquée pour toujours (en attente sur un channel jamais alimenté). Elle ne plante pas, mais consomme de la mémoire indéfiniment. Le contexte (context.Context) sert justement à leur signaler d'abandonner.

Un exercice de découpage concurrent. Cherchez la solution avant d'ouvrir la réponse.

Additionnez le slice []int{1,2,3,4,5,6,7,8} en parallèle :

  • coupez-le en deux moitiés.
  • lancez une goroutine par moitié qui calcule sa somme partielle et l'envoie dans un channel.
  • attendez les deux goroutines avec un sync.WaitGroup, puis additionnez les deux résultats partiels.

Le total attendu est 36.

Indice : passez le *sync.WaitGroup par pointeur, et fermez le channel après wg.Wait().

  • Le mot-clé go lance une goroutine ; elles sont si légères qu'on en lance des milliers.
  • Un channel (make(chan T)) fait communiquer les goroutines ; un channel non bufferisé synchronise.
  • Un pool de workers distribue des jobs via un channel ; on attend la fin avec sync.WaitGroup.
  • Seul l'émetteur ferme un channel ; for range s'arrête à la fermeture, écrire dans un channel fermé panique.
  • Les deux pièges sont le deadlock (attente mutuelle) et la fuite de goroutine bloquée pour toujours.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn