Entretien avec Golang : le modèle de planification GPM de Golang (7)


titre : Modèle de planification GPM de golang (7)
auteur : Russshare
toc : vrai
date : 2021-07-13 19:22:17
tags : [golang, modèle de planification GPM]
catégories : interview de golang

Lien vers le texte original

Si vous ne voulez pas lire le texte, juste ça, c'est vraiment clair
https://www.bilibili.com/video/BV19r4y1w7Nx?from=search&seid=8091821917273363004

https://github.com/qyuhen/book

J'ai toujours été curieux de connaître le mécanisme d'ordonnancement de goroutine.Récemment, je regardais l'analyse du code source golang de Yuhen (basé sur go1.4), et je me suis senti
soudainement éclairé et j'en ai beaucoup profité;

Aller du compliqué au simple, plus une partie de ma propre compréhension, faire le tri

~~

planificateur

Principalement basé sur trois objets de base, G, M, P (définis dans le fichier src/runtime/runtime.h du code source)

  1. G代表一个goroutine对象,每次go调用的时候,都会创建一个G对象
    
  2. M代表一个线程,每次创建一个M的时候,都会有一个底层线程创建;所有的G任务,最终还是在M上执行
    
  3. P代表一个处理器,每一个运行的M都必须绑定一个P,就像线程必须在么一个CPU核上执行一样
    

Le nombre de P est GOMAXPROCS (maximum 256), qui est fixé au démarrage et généralement non modifié ; le nombre de M et le nombre de P peuvent ne pas être aussi nombreux (il peut y avoir des M dormants ou pas trop de M) (maximum 10000 ); chaque P enregistre une file d'attente de tâches G locale et possède également une file d'attente de tâches G globale ;

Comme indiqué ci-dessous

La file d'attente de tâches G globale échangera avec chaque file d'attente de tâches G locale selon une certaine stratégie (si elle est pleine, la moitié de la file d'attente locale sera envoyée à la file d'attente globale)

P est enregistré avec un tableau global (255) et maintient une liste libre globale de P

La file d'attente de tâches G globale échangera avec chaque file d'attente de tâches G locale selon une certaine stratégie (si elle est pleine, la moitié de la file d'attente locale sera envoyée à la file d'attente globale)

P est enregistré avec un tableau global (255) et maintient une liste libre globale de P

Chaque fois que go appelle , il va :

  1. 创建一个G对象,加入到本地队列或者全局队列
    
  2. 如果还有空闲的P,则创建一个M
    
  3. M会启动一个底层线程,循环执行能找到的G任务
    
  4. G任务的执行顺序是,先从本地队列找,本地没有则从全局队列找(一次性转移(全局G个数/P个数)个,再去其它P中找(一次性转移一半),
    
  5. 以上的G任务执行是按照队列顺序(也就是go调用的顺序)执行的。(这个地方是不是觉得很奇怪??)
    

Pour les étapes 2 et 3 ci-dessus, créez un M dont le processus :

  1. 先找到一个空闲的P,如果没有则直接返回,(哈哈,这个地方就保证了进程不会占用超过自己设定的cpu个数)
    
  2. 调用系统api创建线程,不同的操作系统,调用不一样,其实就是和c语言创建过程是一致的,(windows用的是CreateThread,linux用的是clone系统调用),(*^__^*)嘻嘻……
    

3. Ensuite, le thread créé est la vraie chose à faire et exécutez la tâche G en boucle

Il y aura alors un problème. Si un appel système ou une tâche G est exécuté trop longtemps, il occupera toujours ce thread. Étant donné que les tâches G de la file d'attente locale sont exécutées de manière séquentielle, les autres tâches G seront bloquées. longue tâche Chiffon de laine ? (Je cherchais cet endroit depuis longtemps~o(╯□╰)o)

Ainsi, au démarrage, un thread sysmon sera créé spécialement pour le monitoring et la gestion, c'est une boucle en interne :

  1. 记录所有P的G任务计数schedtick,(schedtick会在每执行一个G任务后递增)
    
  2. S'il est vérifié que le schedtick n'a pas été incrémenté, cela signifie que ce P a exécuté la même tâche G. S'il dépasse un certain temps (10ms), ajouter une marque dans les informations de pile de cette tâche G.

  3. Ensuite, lorsque cette tâche G est en cours d'exécution, si elle rencontre un appel de fonction non en ligne, elle vérifiera ce drapeau une fois, puis s'interrompra, s'ajoutera à la fin de la file d'attente et exécutera le prochain G

  4. O(∩_∩)O哈哈~,如果没有遇到非内联函数(有时候正常的小函数会被优化成内联函数)调用的话,那就惨了,会一直执行这个G任务,直到它自己结束;如果是个死循环,并且GOMAXPROCS=1的话,恭喜你,夯住了!亲测,的确如此
    

Pour une tâche G, le processus de reprise après interruption :

  1. 中断的时候将寄存器里的栈信息,保存到自己的G对象里面
    
  2. 当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了。 ~\(≧▽≦)/~
    

Mais il y a un autre problème, qui est le processus de démarrage du système. Yuhen ne l'a pas expliqué trop clairement. Je me suis posé de nombreuses questions (d'où vient le premier M ?, comment G a-t-il trouvé le P correspondant ? etc. ), ça m'a fait mal pendant longtemps ~

Cependant, j'étais obsédé par moi-même, et je l'ajouterai ci-dessous. Tout le monde est invité à me corriger.

  1. 系统启动的时候,首先跑的是主线程,那第一个M应该就是主线程吧(按照C语言的理解,嘿嘿),这里叫M1,可以看前面的图
    
  2. 然后这个主线程会绑定第一个P1
    
  3. 咱们写的main函数,其实是作为一个goroutine来执行的(雨痕说的)
    
  4. 也就是第一个P1就有了一个G1任务,然后第一个M1就执行这个G1任务(也就是main函数),创建这个G1的时候不用创建M了,因为已经有了M1
    
  5. 这个main函数里面所有的goroutine,都绑定到当前的M1所对应的P1上,O(∩_∩)O哈哈~
    
  6. Ensuite, lorsque la goroutine dans le principal est créée (comme G2), un nouveau M2 sera créé. La file d'attente des tâches locales du P2 initial dans le nouveau M2 est vide, et certaines seront prises à partir de P1, haha

  7. 这样两个M1,M2各自执行自己的G任务,再依次往复,这下就圆满了~~~
    

En résumé:

Donc la goroutine est programmée selon le mode préemptif, et une goroutine sera remplacée par la suivante si elle s'exécute au maximum 10ms

Ceci est similaire à la planification du processeur du système grand public actuel (en fonction du découpage du temps)

fenêtres:20ms

Linux:5ms-800ms

C'est presque ici. Celles-ci sont décrites plus en détail dans les notes de Yuhen, mais de nombreux endroits sont désordonnés et compliqués. Il y a beaucoup de projections ici, ce qui est pratique pour les lecteurs à comprendre.

Remarque :

Dans Golang, le compilateur essaiera également d'inline, en copiant et en compilant directement de petites fonctions. Pour inline, essayez d'éliminer le code mort que le compilateur ne peut pas détecter. Utilisez la commande de compilation gobuild -gcflags=-m pour afficher le programme en ligne status , je dois dire que la chaîne d'outils de compilation de golang est toujours très puissante, ce qui est très propice à l'optimisation du programme.

おすすめ

転載: blog.csdn.net/weixin_45264425/article/details/132200046