Les goroutines
Les goroutines dans le langage Go permettent d’exécuter des fonctions de manière concurrente, c’est-à-dire en parallèle, de manière très légère et efficace.
Qu’est-ce qu’une goroutine ?
En termes simples, une goroutine est une fonction qui s’exécute de manière indépendante, en parallèle avec d’autres goroutines, sans les lourdeurs des threads traditionnels des systèmes d’exploitation.
Un thread, ou fil d’exécution, est l’unité la plus petite de traitement que peut gérer un système d’exploitation. Chaque thread représente une séquence d’instructions que le processeur peut exécuter de manière indépendante.
Comment créer une goroutine ?
Le mot clé go
Pour lancer une goroutine, il suffit d’ajouter le mot-clé go devant l’appel de la fonction. Cela signifie que la fonction sera exécutée de manière concurrente par rapport au reste du programme. Dans notre exemple :
package main
import (
"fmt"
"time"
)
// Fonction simple pour afficher un message
func say(message string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(message)
}
}
func main() {
go say("Goroutine : Bonjour")
say("Main : Salut")
}
Étant donné que les deux fonctions s’exécutent en parallèle (une de manière asynchrone et l’autre de manière synchrone), l’ordre dans lequel les messages sont affichés peut varier d’une exécution à l’autre.
Par exemple, il est possible d’obtenir :
Goroutine : Bonjour
Main : Salut
Main : Salut
Goroutine : Bonjour
Goroutine : Bonjour
Main : Salut
ou encore :
Goroutine : Bonjour
Main : Salut
Goroutine : Bonjour
Main : Salut
Main : Salut
Pour coordonner les goroutines, nous utiliserons les channels.
Différence entre une fonction avec ou et sans go
Pour bien comprendre le fonctionnement de go
, nous allons modifier le dernier code, mais cette fois-ci sans utiliser le mot-clé go
.
Cela nous permettra d’observer la différence entre exécuter une fonction normalement et l’exécuter en tant que goroutine.
package main
import (
"fmt"
"time"
)
// Fonction simple pour afficher un message
func say(message string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(message)
}
}
func main() {
say("Goroutine : Bonjour")
say("Main : Salut")
}
Si on lance le programme avec la commande go run main.go
, on tombe systématiquement sur ce résultat :
Goroutine : Bonjour
Goroutine : Bonjour
Goroutine : Bonjour
Main : Salut
Main : Salut
Main : Salut
Il y a donc une différence, car ici, les fonctions sont exécutées les unes après les autres, de manière séquentielle.
Cela signifie que chaque fonction doit terminer son exécution avant que la suivante ne commence.
Cela se voit dans le résultat :
-
Sans go, les messages s’affichent dans un ordre strict, bloquant le programme jusqu’à la fin de chaque fonction.
-
Avec go, les fonctions s’exécutent en parallèle, ce qui peut changer l’ordre d’affichage et accélérer le programme.
Performances des goroutines
Les goroutines sont extrêmement légères en termes de ressources. Vous pouvez en lancer des milliers sans surcharger votre système, ce qui les rend idéales pour gérer des tâches concurrentes comme les requêtes HTTP, les traitements de données en parallèle, etc.
Il est important de noter que, sans mécanisme de synchronisation, le programme principal peut se terminer avant que les goroutines n’aient terminé leur exécution.
C’est là que les channels entrent en jeu, pour synchroniser et communiquer entre les goroutines.
Et nous allons aborder dans le prochain chapitre l'utilisation des channels.