Concurrence et multithreading en Go : goroutines et canaux

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

Introduction à la concurrence en Go

Le langage Go (Golang) a été développé chez Google en tenant compte des exigences modernes en matière de performance et d'évolutivité. L'une des caractéristiques clés de Go est le support intégré de la concurrence (concurrency) — la capacité d'un programme à exécuter plusieurs tâches simultanément. Contrairement au multithreading traditionnel, où les threads sont gérés par le système d'exploitation, Go propose des goroutines légères et des primitives de synchronisation pratiques — les canaux.

Dans cet article, nous aborderons :

  • Ce que sont les goroutines et comment les lancer
  • Les canaux pour échanger des données entre goroutines
  • Les mutex pour un accès sécurisé aux ressources partagées
  • Des exemples pratiques et des modèles de code concurrent


1. Goroutines : les threads légers de Go

Une goroutine est une fonction qui s'exécute de manière concurrente avec d'autres goroutines. Elle n'occupe que quelques kilo-octets de pile (contrairement aux threads du système d'exploitation, dont la pile peut atteindre plusieurs méga-octets). Lancer une goroutine est simple : il suffit d'ajouter le mot-clé go avant l'appel de la fonction.



Exemple : Lancement de goroutines

package main

import ( "fmt" "time")

func sayHello(name string) { for i := 0; i < 3; i++ { fmt.Printf("Bonjour, %s ! (itération %d)", name, i) time.Sleep(100 * time.Millisecond) }}

func main() { go sayHello("Alice") // Lancement d'une goroutine go sayHello("Bob") // Une autre goroutine time.Sleep(1 * time.Second) // On attend la fin fmt.Println("Fonction principale terminée")}

Important : Le programme se termine lorsque la fonction main se termine, même si d'autres goroutines sont encore en cours d'exécution. C'est pourquoi nous utilisons time.Sleep pour attendre. Dans du code réel, il est préférable d'utiliser sync.WaitGroup.



Utilisation de sync.WaitGroup

package main

import ( "fmt" "sync")

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) // ... un certain travail ... 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 de lancer la goroutine go worker(i, &wg) }

wg.Wait() // Bloque main jusqu'à la fin de toutes les goroutines fmt.Println("Tous les travailleurs ont terminé leur travail")}


2. Canaux : échange de données entre goroutines

Les canaux (channels) sont des "tuyaux" typés à travers lesquels les goroutines peuvent envoyer et recevoir des valeurs. Les canaux assurent une synchronisation sécurisée sans verrous explicites.



Création et utilisation des canaux

package main

import "fmt"

func main() { // Création d'un canal pour les entiers ch := make(chan int)

// Lancement d'une goroutine qui envoie des données go func() { ch <- 42 // Envoi d'une valeur dans le canal }()

// Réception de la valeur du canal value := <-ch fmt.Println("Reçu :", value)}

Par défaut, les canaux sont non tamponnés : l'envoi est bloqué jusqu'à ce que le récepteur soit prêt à recevoir les données, et vice versa. Cela assure la synchronisation.



Canaux tamponnés

package main

import "fmt"

func main() { // Canal avec un tampon de 3 éléments ch := make(chan string, 3)

// Envoi sans blocage (tant que le tampon n'est pas plein) ch <- "un" ch <- "deux" ch <- "trois"

// Lecture fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch)}


Fermeture des canaux et range

package main

import "fmt"

func fibonacci(n int, ch chan int) { a, b := 0, 1 for i := 0; i < n; i++ { ch <-

Blogs

Book Recommendations