Maison > Article > développement back-end > Le mécanisme de mise en œuvre de select dans Golang
Ce qui suit est une explication détaillée du mécanisme de mise en œuvre de select dans Golang à partir de la colonne tutoriel golang J'espère que cela sera utile aux amis dans le besoin !
Je joue aujourd'hui J'ai trouvé un problème lors de la sélection, comme suit :
Fragment 1 :
func main(){ var count int for { select { case <-time.Tick(time.Millisecond * 500): fmt.Println("咖啡色的羊驼") count++ fmt.Println("count--->" , count) case <-time.Tick(time.Millisecond * 499) : fmt.Println(time.Now().Unix()) count++ fmt.Println("count--->" , count) } } }
Fragment 2 :
func main(){ t1 := time.Tick(time.Second) t2 := time.Tick(time.Second) var count int for { select { case <-t1: fmt.Println("咖啡色的羊驼") count++ fmt.Println("count--->" , count) case <-t2 : fmt.Println(time.Now().Unix()) count++ fmt.Println("count--->" , count) } } }
Deux questions :
1. dehors?
2. Comment expliquer ?
Le premier problème est facile à résoudre. Il suffit de l'exécuter et il est évident que les résultats de sortie sont définitivement différents.
Fragment 1 :
1535673600 count---> 1 1535673600 count---> 2 1535673601 count---> 3
Fragment 2 :
咖啡色的羊驼 count---> 1 1535673600 count---> 2 咖啡色的羊驼 count---> 3 1535673601 count---> 4
Le second est facile à comprendre, car select surveille les chaînes de deux fois, donc elles apparaissent alternativement.
Alors pourquoi le premier n'apparaît-il qu'une seule fois ?
Afin de résoudre ce problème, j'ai dû réviser le mécanisme d'implémentation de select, j'ai donc rédigé cet article.
Select a plusieurs mécanismes qui nécessitent une attention
1.select+case est utilisé pour bloquer la goroutine d'écoute S'il n'y a pas de cas, juste une sélection. { }, il s'agit de surveiller la goroutine dans le programme actuel. À ce stade, veuillez noter qu'il doit y avoir une vraie goroutine en cours d'exécution, sinon select{} signalera une panique
S'il y a plusieurs exécutables. Dans les cas sélectionnés, l'exécution sera aléatoire.
3.Select coopère souvent avec la boucle for pour surveiller s'il y a une histoire qui se passe sur la chaîne. Il convient de noter que dans ce scénario, break ne quitte que la sélection actuelle et ne sort pas pour. Vous devez utiliser break TIP / goto.
4. Si un canal sans mise en mémoire tampon se ferme immédiatement après avoir transmis la valeur, il se bloquera avant de se fermer. Si un canal a une mise en mémoire tampon, il continuera à recevoir les valeurs suivantes même s'il est fermé.
5. Plusieurs goroutines dans le même canal peuvent être fermées. Vous pouvez utiliser la méthode de panique de récupération pour déterminer le problème de fermeture du canal Après avoir lu les points de connaissances ci-dessus, je ne peux toujours pas expliquer les principaux doutes. de cet article, alors continuez ! Explication détaillée du mécanisme de sélectionVous pouvez consulter /src/runtime/select.go pour en savoir plus sur le mécanisme de sélection. Interprétation des extraits de code source :func selectgo(sel *hselect) int { // ... // case洗牌 pollslice := slice{unsafe.Pointer(sel.pollorder), int(sel.ncase), int(sel.ncase)} pollorder := *(*[]uint16)(unsafe.Pointer(&pollslice)) for i := 1; i < int(sel.ncase); i++ { //.... } // 给case排序 lockslice := slice{unsafe.Pointer(sel.lockorder), int(sel.ncase), int(sel.ncase)} lockorder := *(*[]uint16)(unsafe.Pointer(&lockslice)) for i := 0; i < int(sel.ncase); i++ { // ... } for i := int(sel.ncase) - 1; i >= 0; i-- { // ... } // 加锁该select中所有的channel sellock(scases, lockorder) // 进入loop loop: // ... // pass 1 - look for something already waiting // 按顺序遍历case来寻找可执行的case for i := 0; i < int(sel.ncase); i++ { //... switch cas.kind { case caseNil: continue case caseRecv: // ... goto xxx case caseSend: // ... goto xxx case caseDefault: dfli = casi dfl = cas } } // 没有找到可以执行的case,但有default条件,这个if里就会直接退出了。 if dfl != nil { // ... } // ... // pass 2 - enqueue on all chans // chan入等待队列 for _, casei := range lockorder { // ... switch cas.kind { case caseRecv: c.recvq.enqueue(sg) case caseSend: c.sendq.enqueue(sg) } } // wait for someone to wake us up // 等待被唤起,同时解锁channel(selparkcommit这里实现的) gp.param = nil gopark(selparkcommit, nil, "select", traceEvGoBlockSelect, 1) // 突然有故事发生,被唤醒,再次该select下全部channel加锁 sellock(scases, lockorder) // pass 3 - dequeue from unsuccessful chans // 本轮最后一次循环操作,获取可执行case,其余全部出队列丢弃 casi = -1 cas = nil sglist = gp.waiting // Clear all elem before unlinking from gp.waiting. for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink { sg1.isSelect = false sg1.elem = nil sg1.c = nil } gp.waiting = nil for _, casei := range lockorder { // ... if sg == sglist { // sg has already been dequeued by the G that woke us up. casi = int(casei) cas = k } else { c = k.c if k.kind == caseSend { c.sendq.dequeueSudoG(sglist) } else { c.recvq.dequeueSudoG(sglist) } } // ... } // 没有的话,再走一次loop if cas == nil { goto loop } // ... bufrecv: // can receive from buffer bufsend: // ... recv: // ... rclose: // ... send: // ... retc: // ... sclose: // send on closed channel }Pour faciliter l'affichage, j'ai spécialement réalisé une image moche pour illustrer le processus : Autrement dit, la sélection s'effectue en quatre étapes. Le point clé de doute dans cet article est que dans cette boucle, lorsqu'un exécutable est trouvé,
Les canaux correspondant aux cas qui ne seront pas exécutés dans cette sélection donneront la goroutine actuelle du team. Si vous les ignorez, vous perdrez . Puisque time.Tick est créé sur place dans le cas, au lieu d'être dans la pile globale comme le fragment 2, chaque fois que l'un est exécuté, l'autre est abandonné. , lorsque vous le sélectionnez à nouveau, vous devez le récupérer, il est nouveau et vous devez recommencer.
Voici ma compréhension actuelle. Si vous avez une meilleure compréhension, veuillez me laisser un message, merci.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!