Concurrence vs parallélisme
Imaginez un cuisinier seul dans une cuisine. Il peut préparer la salade pendant que l'eau bout et que le poulet est au four. Il ne fait qu'une chose à la fois, mais il jongle entre plusieurs tâches. C'est la concurrence.
Maintenant imaginez trois cuisiniers qui préparent chacun un plat en même temps. C'est le parallélisme.
Go excelle dans les deux. Et il rend la concurrence ridiculement simple avec un seul mot-clé : go.
Les goroutines : la concurrence en un mot
Une goroutine est une fonction qui s'exécute en arrière-plan. Pour la lancer, ajoutez go devant l'appel :
func afficher(msg string) {
for i := 0; i < 3; i++ {
fmt.Println(msg)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go afficher("goroutine") // S'exécute en arrière-plan
afficher("main") // S'exécute au premier plan
}
Les deux fonctions s'exécutent en même temps. La sortie peut varier à chaque exécution :
Sortie possible :
main
goroutine
goroutine
main
main
goroutine
Une goroutine coûte environ 2 Ko de mémoire. Un thread système en coûte 1 Mo. Vous pouvez créer des millions de goroutines sans problème — c'est comme ça que Go gère des milliers de connexions simultanées.
Les channels : communiquer entre goroutines
Les goroutines travaillent en parallèle, mais comment échangent-elles des données ? Via des channels — des tuyaux typés :
func calculer(ch chan int) {
resultat := 42 * 2
ch <- resultat // Envoyer dans le channel
}
func main() {
ch := make(chan int) // Créer un channel d'entiers
go calculer(ch) // Lancer la goroutine
resultat := <-ch // Recevoir du channel (bloquant)
fmt.Println(resultat) // 84
}
L'opérateur <- fonctionne dans les deux sens :
ch <- valeur— envoyer une valeur dans le channelvaleur := <-ch— recevoir une valeur du channel
La réception est bloquante : le programme attend jusqu'à ce qu'une valeur arrive. Pas besoin de mutex ou de verrous compliqués.
Le proverbe de Go : "Ne communiquez pas en partageant de la mémoire. Partagez de la mémoire en communiquant."
select : écouter plusieurs channels
select permet d'attendre sur plusieurs channels en même temps, comme un switch pour les channels :
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "résultat 1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "résultat 2"
}()
select {
case msg := <-ch1:
fmt.Println("Reçu de ch1:", msg)
case msg := <-ch2:
fmt.Println("Reçu de ch2:", msg)
}
}
select exécute le premier channel qui est prêt. C'est la base des serveurs web performants et des systèmes temps réel.
sync.WaitGroup : attendre toutes les goroutines
Comment savoir quand toutes vos goroutines ont terminé ? Avec sync.WaitGroup :
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // +1 goroutine à attendre
go func(n int) {
defer wg.Done() // -1 quand terminé
fmt.Printf("Tâche %d terminée\n", n)
}(i)
}
wg.Wait() // Attendre que toutes soient terminées
fmt.Println("Tout est fini !")
}
Trois méthodes : Add(n) ajoute n tâches, Done() signale qu'une tâche est terminée, Wait() bloque jusqu'à ce que le compteur atteigne zéro.
Concurrency vs parallelism
Imagine a single cook in a kitchen. They can prepare the salad while the water boils and the chicken is in the oven. They only do one thing at a time, but they juggle multiple tasks. That's concurrency.
Now imagine three cooks each preparing a dish at the same time. That's parallelism.
Go excels at both. And it makes concurrency ridiculously simple with a single keyword: go.
Goroutines: concurrency in one word
A goroutine is a function that runs in the background. To launch it, add go before the call:
func display(msg string) {
for i := 0; i < 3; i++ {
fmt.Println(msg)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go display("goroutine") // Runs in the background
display("main") // Runs in the foreground
}
Both functions execute at the same time. The output may vary each time:
Possible output:
main
goroutine
goroutine
main
main
goroutine
A goroutine costs about 2 KB of memory. A system thread costs 1 MB. You can create millions of goroutines without issues — that's how Go handles thousands of simultaneous connections.
Channels: communicating between goroutines
Goroutines work in parallel, but how do they exchange data? Through channels — typed pipes:
func compute(ch chan int) {
result := 42 * 2
ch <- result // Send to the channel
}
func main() {
ch := make(chan int) // Create a channel of integers
go compute(ch) // Launch the goroutine
result := <-ch // Receive from channel (blocking)
fmt.Println(result) // 84
}
The <- operator works both ways:
ch <- value— send a value to the channelvalue := <-ch— receive a value from the channel
Receiving is blocking: the program waits until a value arrives. No need for mutexes or complicated locks.
Go's proverb: "Don't communicate by sharing memory. Share memory by communicating."
select: listening to multiple channels
select lets you wait on multiple channels at once, like a switch for channels:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "result 1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "result 2"
}()
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
}
}
select executes the first channel that's ready. It's the foundation of performant web servers and real-time systems.
sync.WaitGroup: waiting for all goroutines
How do you know when all your goroutines are done? With sync.WaitGroup:
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // +1 goroutine to wait for
go func(n int) {
defer wg.Done() // -1 when done
fmt.Printf("Task %d completed\n", n)
}(i)
}
wg.Wait() // Wait until all are done
fmt.Println("All done!")
}
Three methods: Add(n) adds n tasks, Done() signals a task is complete, Wait() blocks until the counter reaches zero.
Testez les goroutines et channels dans le Go Playground :
Copiez ce prompt dans Claude ou ChatGPT :
Écris un programme Go qui télécharge 5 URLs en parallèle avec des goroutines et channels. Affiche le temps de réponse de chaque URL. Utilise un WaitGroup.