次のコラム golang チュートリアル では、Golang での select の実装メカニズムについて詳しく説明します。困っている友人の役に立てば幸いです。
#本文 今日のプレイについて言えば、問題フラグメント 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) } } }フラグメント 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) } } }2 つの質問:
1. 上記のフラグメントの出力は次のようになります。 ? ###2.どう説明すればいいでしょうか?
最初の問題は簡単に解決できるので実行してみると、出力結果が明らかに違うことがわかります。
1535673600 count---> 1 1535673600 count---> 2 1535673601 count---> 3
咖啡色的羊驼 count---> 1 1535673600 count---> 2 咖啡色的羊驼 count---> 3 1535673601 count---> 4
2 番目のチャネルは、select が 2 つの時間チャネルを監視するため、交互に表示されるため、理解しやすいです。
では、なぜ最初のものには 1 だけが表示されるのでしょうか?この問題を解決するには、select の実装機構を見直す必要があるため、この記事を作成しました。
Select メカニズムの簡単な説明
2 を報告します。select の下に複数の実行可能ケースがある場合、 、それらはランダムに実行されます。
3.Select は多くの場合、for ループと連携して、チャネルでストーリーが発生しているかどうかを監視します。このシナリオでは、break は現在の選択を終了するだけであり、終了するわけではないことに注意してください。break TIP / goto を使用する必要があります。
4. バッファリングのないチャネルは値を渡した直後に閉じると、閉じる前にブロックされますが、バッファリングのあるチャネルの場合は閉じても後続の値を受け取り続けます。
5 . 同じチャネル内の複数のゴルーチンが閉じられています。リカバリ パニック メソッドを使用して、チャネルが閉じている問題を特定できます。上記の知識ポイントを読んだ後でも、この問題の核心的な疑問を説明できません。記事なので続きをどうぞ! 選択メカニズムの詳細な説明選択メカニズムについては、/src/runtime/select.go を参照してください。 ソース コード スニペットの解釈: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 }表示を容易にするために、プロセスを説明するために特別に醜い図を作成しました:
つまり、4段階で選定が行われます。
この記事の重要な疑問点は、そのループ内で実行可能ファイルが見つかったときに、この選択で実行されないケースに対応するチャネルが、チーム。それらに関係なく、
は失われます。time.Tick は、フラグメント 2 のようなグローバル スタックではなく、ケース内でオンサイトで作成されるため、どちらかが実行されるたびに、もう一方は破棄されます。もう一度選択すると、もう一度取得する必要があり、新しいものなので最初からやり直す必要があります。これは私の現時点での理解です。もっと詳しく理解している場合は、メッセージを残してください。ありがとうございます。
以上がGolang における select の実装メカニズムの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。