Concurrence et multithreading en Go : de A à Z

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

Concurrence et multithreading en Go : de A à Z



Go (Golang) a été conçu en tenant compte des charges de calcul modernes. À l'ère des processeurs multicœurs et des services cloud, la capacité à travailler efficacement avec la concurrence est devenue une compétence obligatoire pour tout développeur backend. Dans cet article, nous allons voir comment Go résout les problèmes d'exécution parallèle du code, en quoi les goroutines diffèrent des threads du système d'exploitation, et comment utiliser correctement les canaux et les mutex.



1. Goroutines : des threads légers



Une goroutine est une fonction ou une méthode qui s'exécute de manière concurrente avec d'autres goroutines. Contrairement aux threads du système d'exploitation, les goroutines ont une surcharge minimale : leur pile commence à 2-4 Ko et peut croître dynamiquement. Lancer une goroutine est incroyablement simple : il suffit d'ajouter le mot-clé go avant l'appel de la fonction.



package main

import ( "fmt" "time")

func say(text string) { for i := 0; i < 3; i++ { fmt.Println(text) time.Sleep(100 * time.Millisecond) }}

func main() { go say("Bonjour depuis la goroutine") say("Bonjour depuis main") // Donnons du temps à la goroutine pour se terminer time.Sleep(500 * time.Millisecond)}


Notez que le programme se termine immédiatement après l'exécution de la fonction main(), sans attendre la fin des goroutines lancées. Pour la synchronisation, nous utilisons time.Sleep (dans les projets réels, on utilise sync.WaitGroup ou des canaux).



2. Canaux : transfert sécurisé de données



Les canaux (channels) sont des "tuyaux" typés à travers lesquels les goroutines échangent des données. Ils résolvent deux problèmes clés : la synchronisation de l'exécution et le transfert de valeurs. Les canaux peuvent être tamponnés ou non tamponnés.



Canal non tamponné

L'opération d'envoi bloque la goroutine jusqu'à ce qu'une autre goroutine effectue une lecture. Cela assure une synchronisation "à la volée".



package main

import "fmt"

func main() { ch := make(chan string)

go func() { ch <- "Bonjour depuis la goroutine !" }()

msg := <-ch fmt.Println(msg)}


Canal tamponné

Un canal avec tampon permet d'envoyer plusieurs valeurs sans lecture immédiate, jusqu'à ce que le tampon soit plein.



package main

import "fmt"

func main() { ch := make(chan int, 2) ch <- 10 ch <- 20 // ch <- 30 // deadlock ! tampon plein

fmt.Println(<-ch) // 10 fmt.Println(<-ch) // 20}


Range et close

La fermeture d'un canal signale aux récepteurs qu'il n'y aura plus de données. La boucle for range se termine automatiquement à la fermeture du canal.



package main

import "fmt"

func producer(ch chan int) { for i := 1; i <= 5; i++ { ch <- i } close(ch)}

func main() { ch := make(chan int) go producer(ch)

for val := range ch { fmt.Println("Reçu :", val) }}


3. Synchronisation et mutex



Malgré la puissance des canaux, un accès direct aux données partagées est parfois nécessaire. Le package sync fournit des mutex (sync.Mutex) et des mutex de lecture (sync.RWMutex) pour protéger les sections critiques.



package main

import ( "fmt" "sync")

var counter = 0var mu sync.Mutex

func increment(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() counter++ mu.Unlock()}

func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go increment(&wg) } wg.Wait() fmt.Println("Compteur final :", counter)}


Ici, sync.WaitGroup attend la fin de toutes les goroutines, et le mutex empêche la condition de course (data race). Sans le mutex, le résultat serait imprévisible.



4. Motifs de concurrence

Blogs

Book Recommendations