Concurrence en Go : goroutines, canaux et async/await (comparaison)

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

Pourquoi Go a-t-il surpassé tout le monde en matière de concurrence ?

\n\n

Imaginez un chef cuisinier qui prépare dix plats simultanément. Il ne court pas d'une plaque à l'autre à une vitesse folle — il alterne efficacement entre les tâches : pendant que la viande mijote, il coupe la salade ; pendant que l'eau bout, il dresse l'assiette. Dans le monde de la programmation, cela s'appelle la concurrence — la capacité à gérer plusieurs tâches sans attendre la fin de chacune.

\n\n

Le langage Go (Golang) est né précisément pour résoudre les problèmes de concurrence. Ses créateurs de Google en avaient assez des complexités du multithreading en C++ et Java. Ils ont inventé les goroutines — des threads légers qui se lancent avec une seule ligne de code. Dans cet article, nous allons voir comment fonctionne la concurrence en Go, la comparer aux threads OS et au paradigme async/await de Python, et surtout montrer des exemples de code réels que vous pourrez appliquer dès aujourd'hui.

\n\n

Goroutines vs Threads OS : la bataille de la cavalerie légère

\n\n

Lorsqu'un développeur entend « multithreading », il imagine généralement les threads du système d'exploitation. Mais en Go, c'est différent. Analysons les différences clés.

\n\n

Qu'est-ce qu'un thread OS ?

\n

Un thread est un objet lourd géré par le noyau de l'OS. Chaque thread possède sa propre pile (généralement 1-8 Mo), nécessite des appels système pour sa création et son changement de contexte. Créer 10 000 threads signifie tuer la mémoire et le processeur. Go repose sur une philosophie différente.

\n\n

Goroutine — le thread léger de Go

\n

Une goroutine est une fonction qui s'exécute de manière concurrente. Elle est lancée par le mot-clé go. Ses principales caractéristiques :

\n
    \n
  • Taille de la pile : commence à 2-4 Ko et croît/diminue dynamiquement.
  • \n
  • Création : des milliers de goroutines sont créées en quelques millisecondes.
  • \n
  • Planificateur : le runtime Go répartit lui-même les goroutines sur les threads OS (généralement leur nombre est égal au nombre de cœurs CPU).
  • \n
  • Syntaxe : ajoutez simplement go avant l'appel d'une fonction.
  • \n
\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc sayHello(name string) {\n    for i := 0; i < 3; i++ {\n        fmt.Println("Bonjour,", name)\n        time.Sleep(100 * time.Millisecond)\n    }\n}\n\nfunc main() {\n    // Lancement de deux goroutines\n    go sayHello("Go")\n    go sayHello("Goroutine")\n\n    // On laisse le temps aux goroutines de s'exécuter\n    time.Sleep(1 * time.Second)\n    fmt.Println("Fonction principale terminée")\n}
\n\n

Résultat : les messages des deux goroutines seront mélangés. Go alterne automatiquement entre elles, sans se bloquer mutuellement. Essayez d'exécuter ce code — vous verrez « Bonjour, Go » et « Bonjour, Goroutine » s'afficher en alternance.

\n\n

Canaux : l'outil principal de synchronisation

\n\n

Les goroutines, c'est bien, mais comment communiquent-elles ? En Go, il n'y a pas de mémoire partagée par défaut. On utilise plutôt le principe : « Ne communiquez pas en partageant la mémoire ; partagez la mémoire en communiquant ». Pour cela, on utilise les canaux (channels).

\n\n

Un canal est un « pipeline » typé à travers lequel les goroutines envoient et reçoivent des données. C'est sûr, simple et élégant.

\n\n

Exemple : traitement parallèle de nombres

\n
package main\n\nimport "fmt"\n\nfunc squareWorker(numbers <-chan int, results chan<- int) {\n    for n := range numbers {\n        results <- n * n\n    }\n    close(results)\n}\n\nfunc main() {\n    numbers := make(chan int, 5)\n    results := make(chan int, 5)\n\n    // Lancement du worker\n    go squareWorker(numbers, results)\n\n    // Envoi des données\n    numbers <- 2\n    numbers <- 4\n    numbers <- 6\n    close(numbers)\n\n    // Lecture des résultats\n    for res := range results {\n        fmt.Println("Carré :", res)\n    }\n}
\n\n

Dans cet exemple, nous utilisons des canaux tamponnés (le deuxième argument est la taille du tampon). La goroutine worker lit depuis le canal numbers, élève au carré et envoie le résultat dans le canal results. La fonction principale lit ensuite les résultats.

Blogs

Book Recommendations