Une goroutine est un léger thread d'exécution géré par le runtime de Go. Pour lancer une nouvelle goroutine, on utilise le mot-clé go avant l'appel d'une fonction. En coulisses, une structure décrivant la pile et l'état de la tâche est créée et ajoutée à la file d'attente du planificateur de goroutines.
Contrairement aux threads du système d'exploitation, une goroutine a une pile initiale beaucoup plus petite (généralement 2 Ko), et la pile s'étend automatiquement si nécessaire. Le planificateur de Go les répartit lui-même sur les threads disponibles du système d'exploitation (modèle M:N).
Les principales différences par rapport aux threads :
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d a commencé le travail %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d a terminé le travail %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { <-results } }
Les goroutines Go peuvent-elles s'exécuter en parallèle sur plusieurs cœurs ?
Il est souvent répondu à tort : "Non, parce que Go utilise des threads verts". En réalité, grâce à une variable d'environnement ou à l'appel de runtime.GOMAXPROCS(n), Go peut paralléliser l'exécution des goroutines sur tous les cœurs disponibles du processeur.
import "runtime" func main() { runtime.GOMAXPROCS(4) // Permet d'utiliser 4 cœurs ... }
Histoire
Dans un projet de service backend en Go, un pool de workers a été mis en place via des goroutines, mais les programmeurs ont oublié de limiter le nombre de goroutines en cours d'exécution. En conséquence, lors d'une augmentation de la charge, l'application lançait des milliers de goroutines, ce qui entraînait une épuisement de la mémoire et un crash du service. Le problème a été résolu par l'introduction d'une limite sur les goroutines actives (par exemple, à l'aide d'un sémaphore ou d'un pool de workers).
Histoire
Un des employés a mal synchronisé les données entre les goroutines, en utilisant de simples variables globales sans mutex ni canaux. Cela a provoqué une condition de course, entraînant des erreurs périodiques lors du traitement des paiements. Le problème n'a été découvert qu'après le déploiement en production.
Histoire
Dans un service de parsing, le moment de passer des canaux nil dans select a été négligé : après la fermeture du canal, select continuait à attendre bloqueur des données, et certaines goroutines se sont "accrochées". Cela a été corrigé par l'attribution de nil au canal fermé et le traitement approprié de select.