In der folgenden Kolumne finden Sie eine detaillierte Erklärung des Implementierungsmechanismus von select in Golang aus der Kolumne Golang-Tutorial. Ich hoffe, dass es für Freunde hilfreich ist, die es brauchen!
Ich habe heute beim Spielen von Select ein Problem festgestellt, es ist so:
Clip 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) } } }
Clip 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) } } }
Zwei Fragen:
1. Was ist die Ausgabe des obigen Snippets?
2.Wie soll ich es erklären?
Das erste Problem ist einfach zu lösen. Es ist offensichtlich, dass die Ausgabeergebnisse definitiv unterschiedlich sind.
Clip 1:
1535673600 count---> 1 1535673600 count---> 2 1535673601 count---> 3
Clip 2:
咖啡色的羊驼 count---> 1 1535673600 count---> 2 咖啡色的羊驼 count---> 3 1535673601 count---> 4
Der zweite ist leicht zu verstehen, da select die Kanäle zweimal überwacht, sodass sie abwechselnd angezeigt werden.
Warum erscheint also nur einer im ersten Teil?
Um dieses Problem zu lösen, musste ich den Implementierungsmechanismus von select überarbeiten, also habe ich diesen Artikel verfasst.
Select verfügt über mehrere Mechanismen, die beachtet werden müssen
1.select+case wird zum Blockieren und Überwachen von Goroutine verwendet. Wenn kein case vorhanden ist, überwacht nur ein select{} die Goroutine im aktuellen Programm . Bitte beachten Sie zu diesem Zeitpunkt, dass eine echte Goroutine ausgeführt werden muss, andernfalls meldet select{} Panik
2. Wenn unter select mehrere ausführbare Fälle vorhanden sind, wird einer zufällig ausgeführt.
3.Select arbeitet oft mit for-Schleifen, um zu überwachen, ob auf dem Kanal Geschichten passieren. Es ist zu beachten, dass break in diesem Szenario nur die aktuelle Auswahl verlässt und nicht beendet wird. Sie müssen break TIP / goto verwenden.
4. Wenn der Kanal ohne Pufferung unmittelbar nach der Übergabe des Werts geschlossen wird, wird er vor dem Schließen weiterhin empfangen, auch wenn mehrere Goroutinen aktiviert sind Wenn Sie den gleichen Kanal schließen, können Sie die Wiederherstellungs-Panik-Methode verwenden, um das Problem der Kanalschließung zu ermitteln.
Nachdem ich die oben genannten Wissenspunkte gelesen habe, kann ich die Kernzweifel dieses Artikels immer noch nicht erklären, also fahren Sie fort!
Detaillierte Erklärung des Auswahlmechanismus
Interpretation von Quellcode-Schnipseln:
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 }
Zur besseren Darstellung habe ich extra ein hässliches Bild angefertigt, um den Vorgang zu veranschaulichen:
Mit anderen Worten, die Auswahl erfolgt in vier Schritten.
Der Hauptzweifelpunkt in diesem Artikel ist, dass in dieser Schleife, wenn eine ausführbare Datei gefunden wird,
Die Kanäle, die den Fällen entsprechen, die in dieser Auswahl nicht ausgeführt werden, der aktuellen Goroutine des Teams übergeben werden, und das ist auch der Fall ignoriert und verloren., da time.Tick an Ort und Stelle im Fall erstellt wird, anstatt sich wie Fragment 2 im globalen Stapel zu befinden, wird also jedes Mal, wenn eines ausgeführt wird, das andere aufgegeben und muss erneut erstellt werden - erneut ausgewählt. Um etwas Neues zu erhalten, müssen Sie noch einmal von vorne beginnen. Dies ist mein vorübergehendes Verständnis. Wenn Sie es besser verstehen, hinterlassen Sie mir bitte eine Nachricht. Vielen Dank.
Das obige ist der detaillierte Inhalt vonDer Implementierungsmechanismus von select in Golang. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!