Parlez de la planification des goroutines en Go.
sobes.tech IA
Réponse de l'IA
Le planificateur de goroutines en Go utilise le modèle M:N, où M goroutines sont mappées sur N threads du système d'exploitation. Il bascule efficacement les goroutines entre les threads, implémentant une multitâche sans préemption avec des points d'interruption (points de préemption).
Composants principaux du modèle GPM:
- G (Goroutine): Abstraction de l'unité de code en exécution, thread léger géré par le planificateur de Go.
- P (Processeur): Processeur logique, représentant un contexte de file d'attente locale de goroutines et de ressources telles que mutex et planificateur. La quantité de P par défaut est déterminée par le nombre de cœurs logiques du processeur, mais peut être modifiée via
GOMAXPROCS. - M (Machine/Thread): Thread du système d'exploitation qui exécute le code. M est associé à P et exécute des goroutines depuis la file locale de P ou la file globale.
Mode de fonctionnement:
- Une nouvelle goroutine est créée et placée dans la file locale d'un P disponible.
- M, associé à P, prend une goroutine de la file locale et l'exécute.
- Si la file locale est vide, M tente de voler des goroutines d'autres P.
- Si P se bloque (par exemple, lors d'un appel système), M associé se détache, et un autre M s'associe à ce P. M bloqué peut être réutilisé lorsque l'appel système est terminé.
- Si une goroutine se bloque sur un canal ou un mutex, elle est mise en file d'attente, et le planificateur choisit une autre goroutine pour s'exécuter sur ce même M.
- Le planificateur effectue des vérifications périodiques pour déloger les goroutines qui tournent longtemps, permettant à d'autres goroutines d'obtenir du temps CPU.
Les points de préemption se produisent à des endroits spécifiques:
- Appels de fonctions/méthodes.
- Boucles contenant des appels de fonctions.
- Allocation de mémoire.
- Primitives de synchronisation (canaux, mutex).
Exemple de planification:
package main
import (
"fmt"
"runtime"
"time"
)
func worker(id int) {
fmt.Printf("Goroutine %d commence à travailler\n", id)
time.Sleep(time.Second) // Simulation de travail
fmt.Printf("Goroutine %d a terminé de travailler\n", id)
}
func main() {
runtime.GOMAXPROCS(2) // Définit le nombre de processeurs logiques
for i := 1; i <= 5; i++ {
go worker(i) // Crée des goroutines
}
time.Sleep(3 * time.Second) // Attend la fin des goroutines
fmt.Println("Goroutine principale terminée")
}
Dans cet exemple, 5 goroutines sont planifiées sur 2 processeurs logiques. Le planificateur de Go répartira ces goroutines entre les threads du système d'exploitation disponibles, en utilisant des files d'attente locales et éventuellement globales, ainsi que le mécanisme de "vol" de travail (work stealing).
Avantages de cette approche:
- Scalabilité: Utilisation efficace des systèmes multi-cœurs.
- Faibles coûts: Goroutines légères par rapport aux threads du système d'exploitation.
- Facilité d'utilisation: La planification intégrée au langage rend la programmation parallèle plus accessible.