Maison  >  Article  >  développement back-end  >  À propos de la planification des coroutines Golang

À propos de la planification des coroutines Golang

藏色散人
藏色散人avant
2020-09-01 14:50:022607parcourir

Ce qui suit est une introduction à la planification de coroutines Golang à partir de la colonne tutoriel Golang J'espère que cela sera utile aux amis dans le besoin !

À propos de la planification des coroutines Golang

1. Modèle de thread

  • Modèle N:1, N threads d'espace utilisateur s'exécutent sur 1 thread d'espace noyau. L'avantage est que le changement de contexte est très rapide mais il ne peut pas tirer parti des systèmes multicœurs.
  • Modèle 1:1, 1 thread d'espace noyau exécute 1 thread d'espace utilisateur. Cela tire pleinement parti des avantages des systèmes multicœurs, mais le changement de contexte est très lent, car chaque planification bascule entre le mode utilisateur et le mode noyau. (Modèle de thread POSIX (pthread), Java)
  • Modèle M:N, chaque thread utilisateur correspond à plusieurs threads d'espace noyau, et un thread d'espace noyau peut également correspondre à plusieurs threads d'espace utilisateur. Go a l'intention d'adopter ce modèle et d'utiliser n'importe quel nombre de modèles de noyau pour gérer n'importe quel nombre de goroutines. Celui-ci combine les avantages des deux modèles ci-dessus, mais l’inconvénient est la complexité de la planification.

Jetons un coup d'œil à la planification des coroutines de Golang

  • M : Un thread en espace utilisateur, correspondant à un thread du noyau, similaire au posix pthread
  • P : Représente le contexte d'exécution, qui est le planificateur que nous avons implémenté dans la section précédente. Un planificateur correspondra également à une file d'attente prête
  • G: goroutine, c'est-à-dire coroutine

2. . Introduction au modèle de planification

Groutine peut avoir une implémentation simultanée puissante via le modèle de planification GPM. Expliquons le modèle de planification goroutine ci-dessous.

Le planificateur de Go contient trois structures importantes : M, P, G
M : M est une encapsulation de threads au niveau du noyau, et le nombre correspond au réel Numéro de CPU, un M est un thread et une goroutine s'exécute sur M ; M est une grande structure qui conserve de nombreuses informations telles que le cache de mémoire de petits objets (mcache), la goroutine en cours d'exécution, le générateur de nombres aléatoires, etc.
G : représente une goroutine, qui possède sa propre pile, son pointeur d'instruction et d'autres informations (canaux en attente, etc.) pour la planification.
P : Le nom complet de P est Processor. Son objectif principal est d'exécuter la goroutine. Chaque objet Processeur a une LRQ (Local Run Queue). Les objets Goroutine non alloués sont stockés dans la GRQ (Global Run Queue), en attente d'être alloués à un certain P dans la LRQ. Chaque LRQ contient plusieurs objets Goroutines créés par l'utilisateur.

Golang utilise un modèle multi-threading. Plus en détail, il s'agit d'un modèle de thread à deux niveaux, mais il encapsule les threads système (threads au niveau du noyau) et expose une goroutine de coroutine légère (threads au niveau de l'utilisateur). sont destinés aux utilisateurs, et la planification des threads au niveau de l'utilisateur vers les threads au niveau du noyau est gérée par le runtime de Golang, et la logique de planification est transparente pour le monde extérieur. L'avantage de goroutine est que le changement de contexte est effectué dans l'état utilisateur complet. Il n'est pas nécessaire de basculer entre l'état utilisateur et l'état du noyau aussi souvent que les threads, ce qui permet d'économiser des ressources.

Implémentation de la planification

Comme le montre la figure ci-dessus, il y a 2 threads physiques M, chaque M a un processeur P, et chacun Il y a une goroutine en cours d'exécution.
Le nombre de P peut être défini via GOMAXPROCS(), qui représente en fait la concurrence réelle, c'est-à-dire le nombre de goroutines pouvant s'exécuter en même temps.
Les goroutines grises sur la photo ne fonctionnent pas, mais sont prêtes, en attente d'être programmées. P maintient cette file d'attente (appelée runqueue). Dans le langage Go, il est facile de démarrer une goroutine : fonction just go Ainsi, chaque fois qu'une instruction go est exécutée, une
goroutine est ajoutée à la fin de la file d'attente runqueue. , au point de planification suivant, une goroutine est retirée de la file d'attente (comment décider quelle goroutine prendre ?) et exécutée.

Lorsqu'un thread de système d'exploitation M0 est bloqué (comme indiqué ci-dessous), P exécute M1 à la place. M1 dans l'image peut être créé ou supprimé du cache de threads.

Lorsque MO revient, il doit essayer d'obtenir un P pour exécuter la goroutine. Normalement, il en obtiendra un à partir d'autres threads du système d'exploitation. terminé,

S'il ne l'obtient pas, il mettra la goroutine dans un environnement global runqueue, puis dormir tout seul (mis dans le cache des threads). All P vérifiera également périodiquement global runqueue et exécutez la goroutine dedans, sinon la goroutine sur la file d'exécution globale ne sera jamais exécutée.

Une autre situation est que la tâche G assignée par P est rapidement terminée (répartition inégale), ce qui fait que le processeur P est très occupé, mais d'autres P sont toujours occupés. tâche. À l'heure actuelle, si elle est globale. Il n'y a pas de tâche G dans la file d'exécution, donc P doit récupérer du G auprès d'un autre P pour l'exécuter. D'une manière générale, si P veut obtenir une tâche d'un autre P, il faut généralement l'exécuter la moitié de la file d'attente, ce qui garantit que chaque thread du système d'exploitation peut être pleinement utilisé, comme indiqué ci-dessous :

3. Problèmes liés à la création de GPM

Comment déterminer le nombre de M et P ? Ou quand M et P seront-ils créés ?

1. Le nombre de P :

  • est déterminé par la variable d'environnement $GOMAXPROCS au démarrage ou par la méthode d'exécution GOMAXPROCS() (la valeur par défaut est 1). Cela signifie qu'à tout moment pendant l'exécution du programme, seules les goroutines $GOMAXPROCS s'exécutent en même temps.

2. Le nombre de M :

  • Limitations du langage go lui-même : Lorsque le programme go démarre, le nombre maximum de M sera défini, avec une valeur par défaut de 10 000. Cependant, il est difficile pour le noyau de la prendre en charge. Avec autant de threads, cette limite peut être ignorée.
  • La fonction SetMaxThreads dans runtime/debug définit le nombre maximum de M
  • Si un M est bloqué, un nouveau M sera créé.

Il n'y a pas de relation absolue entre le nombre de M et P. Si un M est bloqué, P créera ou passera à un autre M. Par conséquent, même si le nombre par défaut de P est 1, de nombreux M peut être créé.

3. Lorsque P est créé : Après avoir déterminé le nombre maximum n de P, le système d'exécution créera n Ps en fonction de ce nombre.

4. Lorsque M est créé : il n'y a pas assez de M pour s'associer à P et y exécuter le G exécutable. Par exemple, si tous les M sont bloqués à ce moment-là et qu'il y a encore de nombreuses tâches prêtes dans P, il recherchera un M inactif. S'il n'y a pas de M inactif, un nouveau M sera créé.

Quelle association P M choisit-il ?

  • M sélectionnera l'association P qui a provoqué la création de ce M.

Quand la relation entre P et M sera-t-elle inversée ?

Lorsque M est bloqué en raison d'un appel système (lorsque G exécuté sur M entre dans un appel système), M et P seront séparés s'il y a des tâches dans la file d'attente prête de P à ce moment,
P. Il s'associera à un M inactif ou créera un M pour l'association. (C'est-à-dire que go ne gère pas le blocage des E/S comme libtask ? Pas sûr.)

Comment un G prêt choisit-il la file d'attente prête de P dans laquelle entrer ?

  • Par défaut : Parce que le nombre par défaut de P est 1 (M n'est pas nécessairement 1), si nous ne modifions pas GOMAXPROCS, peu importe le nombre de goroutines que nous créons avec les instructions go dans le programme, elles will Il ne sera inséré que dans la file d'attente prête du même P.
  • Lorsqu'il y a plusieurs P : si GOMAXPROCS est modifié ou si runtime.GOMAXPROCS est appelé, le système d'exécution répartira uniformément tous les G dans la file d'attente prête de chaque P.

Comment s'assurer qu'il y aura G dans la file d'attente prête de chaque P

Si toutes les tâches de la file d'attente prête d'un P ont été exécutées, alors P essaiera d'obtenir G des autres P file d'attente prête. Retirez-en une partie et placez-la dans sa propre file d'attente prête pour vous assurer que la file d'attente prête de chaque P contient des tâches qui peuvent être exécutées.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer