【Go 1.26.4】Golang Select 深度解析

Golang Select 深度解析

基于 Go 1.26.4 源码,源码路径:github.com/go-go1.26.4

核心源文件:runtime/select.goruntime/chan.gocmd/compile/internal/walk/select.go


select 是 Go 语言与switch相似但本质不同的控制结构。每一个 case 必须是 channel 的发送或者接收操作,当多个 channel 同时就绪的时候,select 会随机选择其中一个来执行,这种随机性是为了保证公平------没有任何一个 channel 会被饥饿。

1 select 功能完整介绍

1.1 语法与核心概念

Go 的 select 语句是专门为 channel 设计的多路复用器,类似于 Unix 的 select/poll/epoll

go 复制代码
select {
case v := <-ch1:          // 接收 case
    fmt.Println("received:", v)
case ch2 <- value:         // 发送 case
    fmt.Println("sent:", value)
case v, ok := <-ch3:       // 接收 + ok 判断
    if !ok { fmt.Println("ch3 closed") }
default:                   // 非阻塞
    fmt.Println("no channel ready")
}

1.2 核心语义规则

规则 说明
随机选择 多个 case 同时就绪时,随机选择一个执行
阻塞等待 没有 case 就绪且无 default → 阻塞当前 goroutine
非阻塞 有 default → 无 case 就绪时执行 default
空 select select{} → 永久阻塞(等同于死锁)
nil channel nil channel 的 case 永远不会被选中
close 优先 已关闭的 channel 的接收 case 立刻就绪

#mermaid-svg-B8fbKvo859ByU9QT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-B8fbKvo859ByU9QT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B8fbKvo859ByU9QT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B8fbKvo859ByU9QT .error-icon{fill:#552222;}#mermaid-svg-B8fbKvo859ByU9QT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B8fbKvo859ByU9QT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B8fbKvo859ByU9QT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B8fbKvo859ByU9QT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B8fbKvo859ByU9QT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B8fbKvo859ByU9QT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B8fbKvo859ByU9QT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B8fbKvo859ByU9QT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B8fbKvo859ByU9QT .marker.cross{stroke:#333333;}#mermaid-svg-B8fbKvo859ByU9QT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B8fbKvo859ByU9QT p{margin:0;}#mermaid-svg-B8fbKvo859ByU9QT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B8fbKvo859ByU9QT .cluster-label text{fill:#333;}#mermaid-svg-B8fbKvo859ByU9QT .cluster-label span{color:#333;}#mermaid-svg-B8fbKvo859ByU9QT .cluster-label span p{background-color:transparent;}#mermaid-svg-B8fbKvo859ByU9QT .label text,#mermaid-svg-B8fbKvo859ByU9QT span{fill:#333;color:#333;}#mermaid-svg-B8fbKvo859ByU9QT .node rect,#mermaid-svg-B8fbKvo859ByU9QT .node circle,#mermaid-svg-B8fbKvo859ByU9QT .node ellipse,#mermaid-svg-B8fbKvo859ByU9QT .node polygon,#mermaid-svg-B8fbKvo859ByU9QT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B8fbKvo859ByU9QT .rough-node .label text,#mermaid-svg-B8fbKvo859ByU9QT .node .label text,#mermaid-svg-B8fbKvo859ByU9QT .image-shape .label,#mermaid-svg-B8fbKvo859ByU9QT .icon-shape .label{text-anchor:middle;}#mermaid-svg-B8fbKvo859ByU9QT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-B8fbKvo859ByU9QT .rough-node .label,#mermaid-svg-B8fbKvo859ByU9QT .node .label,#mermaid-svg-B8fbKvo859ByU9QT .image-shape .label,#mermaid-svg-B8fbKvo859ByU9QT .icon-shape .label{text-align:center;}#mermaid-svg-B8fbKvo859ByU9QT .node.clickable{cursor:pointer;}#mermaid-svg-B8fbKvo859ByU9QT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-B8fbKvo859ByU9QT .arrowheadPath{fill:#333333;}#mermaid-svg-B8fbKvo859ByU9QT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B8fbKvo859ByU9QT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B8fbKvo859ByU9QT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B8fbKvo859ByU9QT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-B8fbKvo859ByU9QT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B8fbKvo859ByU9QT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-B8fbKvo859ByU9QT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B8fbKvo859ByU9QT .cluster text{fill:#333;}#mermaid-svg-B8fbKvo859ByU9QT .cluster span{color:#333;}#mermaid-svg-B8fbKvo859ByU9QT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B8fbKvo859ByU9QT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-B8fbKvo859ByU9QT rect.text{fill:none;stroke-width:0;}#mermaid-svg-B8fbKvo859ByU9QT .icon-shape,#mermaid-svg-B8fbKvo859ByU9QT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B8fbKvo859ByU9QT .icon-shape p,#mermaid-svg-B8fbKvo859ByU9QT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-B8fbKvo859ByU9QT .icon-shape .label rect,#mermaid-svg-B8fbKvo859ByU9QT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B8fbKvo859ByU9QT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-B8fbKvo859ByU9QT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-B8fbKvo859ByU9QT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 多个就绪
仅一个就绪
无就绪
Yes
No
select 语句
有 case 就绪?
随机选一个执行

pollorder 随机化
执行该 case
有 default?
执行 default
★ 阻塞当前 G

注册到所有 channel

gopark 挂起
某个 channel 就绪

唤醒 G
从其他 channel 注销

执行选中 case

1.3 适用场景

#mermaid-svg-6y5YQsDiuhIygQmu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6y5YQsDiuhIygQmu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6y5YQsDiuhIygQmu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6y5YQsDiuhIygQmu .error-icon{fill:#552222;}#mermaid-svg-6y5YQsDiuhIygQmu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6y5YQsDiuhIygQmu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6y5YQsDiuhIygQmu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6y5YQsDiuhIygQmu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6y5YQsDiuhIygQmu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6y5YQsDiuhIygQmu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6y5YQsDiuhIygQmu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6y5YQsDiuhIygQmu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6y5YQsDiuhIygQmu .marker.cross{stroke:#333333;}#mermaid-svg-6y5YQsDiuhIygQmu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6y5YQsDiuhIygQmu p{margin:0;}#mermaid-svg-6y5YQsDiuhIygQmu .edge{stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .section--1 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section--1 path,#mermaid-svg-6y5YQsDiuhIygQmu .section--1 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section--1 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section--1 text{fill:#ffffff;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth--1{stroke-width:17;}#mermaid-svg-6y5YQsDiuhIygQmu .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-0 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-0 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-0 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-0 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-0 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-0{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-0{stroke-width:14;}#mermaid-svg-6y5YQsDiuhIygQmu .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-1 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-1 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-1 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-1 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-1 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-1{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-1{stroke-width:11;}#mermaid-svg-6y5YQsDiuhIygQmu .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-2 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-2 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-2 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-2 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-2 text{fill:#ffffff;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-2{stroke-width:8;}#mermaid-svg-6y5YQsDiuhIygQmu .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-3 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-3 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-3 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-3 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-3 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-3{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-3{stroke-width:5;}#mermaid-svg-6y5YQsDiuhIygQmu .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-4 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-4 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-4 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-4 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-4 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-4{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-4{stroke-width:2;}#mermaid-svg-6y5YQsDiuhIygQmu .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-5 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-5 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-5 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-5 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-5 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-5{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-5{stroke-width:-1;}#mermaid-svg-6y5YQsDiuhIygQmu .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-6 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-6 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-6 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-6 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-6 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-6{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-6{stroke-width:-4;}#mermaid-svg-6y5YQsDiuhIygQmu .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-7 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-7 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-7 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-7 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-7 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-7{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-7{stroke-width:-7;}#mermaid-svg-6y5YQsDiuhIygQmu .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-8 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-8 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-8 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-8 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-8 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-8{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-8{stroke-width:-10;}#mermaid-svg-6y5YQsDiuhIygQmu .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-9 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-9 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-9 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-9 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-9 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-9{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-9{stroke-width:-13;}#mermaid-svg-6y5YQsDiuhIygQmu .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-10 rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-10 path,#mermaid-svg-6y5YQsDiuhIygQmu .section-10 circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-10 polygon,#mermaid-svg-6y5YQsDiuhIygQmu .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-10 text{fill:black;}#mermaid-svg-6y5YQsDiuhIygQmu .node-icon-10{font-size:40px;color:black;}#mermaid-svg-6y5YQsDiuhIygQmu .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .edge-depth-10{stroke-width:-16;}#mermaid-svg-6y5YQsDiuhIygQmu .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled,#mermaid-svg-6y5YQsDiuhIygQmu .disabled circle,#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:lightgray;}#mermaid-svg-6y5YQsDiuhIygQmu .disabled text{fill:#efefef;}#mermaid-svg-6y5YQsDiuhIygQmu .section-root rect,#mermaid-svg-6y5YQsDiuhIygQmu .section-root path,#mermaid-svg-6y5YQsDiuhIygQmu .section-root circle,#mermaid-svg-6y5YQsDiuhIygQmu .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-6y5YQsDiuhIygQmu .section-root text{fill:#ffffff;}#mermaid-svg-6y5YQsDiuhIygQmu .section-root span{color:#ffffff;}#mermaid-svg-6y5YQsDiuhIygQmu .section-2 span{color:#ffffff;}#mermaid-svg-6y5YQsDiuhIygQmu .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-6y5YQsDiuhIygQmu .edge{fill:none;}#mermaid-svg-6y5YQsDiuhIygQmu .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-6y5YQsDiuhIygQmu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Go Select 适用场景
超时控制
select + time.After
context.Done
非阻塞收发
default 分支
尝试性操作
多路复用
监听多个 channel
合并数据流
退出信号
done channel
context cancel
限流
ticker + quit
定时任务


2 编译器对 select 的重写优化

源码位置:cmd/compile/internal/walk/select.go

编译器在编译期对 select 做了优化重写,只有通用情况才调用 selectgo

2.1 优化1:零 case select → block()

go 复制代码
// 源码
select {}

// 编译器重写为
runtime.block()
// → gopark(nil, nil, waitReasonSelectNoCases, traceBlockForever, 1)
// 永久阻塞,等价于死锁

2.2 优化2:单 case + 无 default → 直接 channel 操作

go 复制代码
// 源码
select {
case v := <-ch:
    fmt.Println(v)
}

// 编译器重写为:直接调用 chanrecv
v := <-ch
fmt.Println(v)
// 无需 selectgo 的开销

2.3 优化3:单 case + default → 非阻塞操作

go 复制代码
// 源码
select {
case v := <-ch:
    fmt.Println("received:", v)
default:
    fmt.Println("no data")
}

// 编译器重写为:调用 selectnbrecv(非阻塞接收)
if selected, ok := selectnbrecv(&v, ch); selected {
    fmt.Println("received:", v)
} else {
    fmt.Println("no data")
}

// selectnbrecv 内部就是 chanrecv(c, elem, block=false)
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected, received bool) {
    return chanrecv(c, elem, false)
}
go 复制代码
// 发送 + default 的重写
select {
case ch <- value:
    fmt.Println("sent")
default:
    fmt.Println("channel full")
}

// 编译器重写为
if selectnbsend(ch, value) {
    fmt.Println("sent")
} else {
    fmt.Println("channel full")
}

// selectnbsend 内部
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
    return chansend(c, elem, false, sys.GetCallerPC())
}

2.4 通用情况:多 case → selectgo

3 个及以上 case(或有 default 的 2 case)编译器调用 selectgo

go 复制代码
// 编译器生成的伪代码:

// 1. 分配 scase 数组(栈上)
selv := [ncases]scase{
    {c: ch1, elem: &v1},  // case 0: 接收
    {c: ch2, elem: &v2},  // case 1: 发送
    ...
}

// 2. 分配 order 数组(栈上)
order := [2*ncases]uint16{}

// 3. 调用 selectgo
chosen, recvOK := selectgo(&selv[0], &order[0], pc0, nsends, nrecvs, dflt==nil)

// 4. 根据 chosen 分发到对应 case
switch chosen {
case 0:  // 第一个 case 被选中
    // ...
case 1:  // 第二个 case 被选中
    // ...
case -1: // default 被选中
    // ...
}

#mermaid-svg-cSmCHMuE86puT555{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cSmCHMuE86puT555 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cSmCHMuE86puT555 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cSmCHMuE86puT555 .error-icon{fill:#552222;}#mermaid-svg-cSmCHMuE86puT555 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cSmCHMuE86puT555 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cSmCHMuE86puT555 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cSmCHMuE86puT555 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cSmCHMuE86puT555 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cSmCHMuE86puT555 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cSmCHMuE86puT555 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cSmCHMuE86puT555 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cSmCHMuE86puT555 .marker.cross{stroke:#333333;}#mermaid-svg-cSmCHMuE86puT555 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cSmCHMuE86puT555 p{margin:0;}#mermaid-svg-cSmCHMuE86puT555 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cSmCHMuE86puT555 .cluster-label text{fill:#333;}#mermaid-svg-cSmCHMuE86puT555 .cluster-label span{color:#333;}#mermaid-svg-cSmCHMuE86puT555 .cluster-label span p{background-color:transparent;}#mermaid-svg-cSmCHMuE86puT555 .label text,#mermaid-svg-cSmCHMuE86puT555 span{fill:#333;color:#333;}#mermaid-svg-cSmCHMuE86puT555 .node rect,#mermaid-svg-cSmCHMuE86puT555 .node circle,#mermaid-svg-cSmCHMuE86puT555 .node ellipse,#mermaid-svg-cSmCHMuE86puT555 .node polygon,#mermaid-svg-cSmCHMuE86puT555 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cSmCHMuE86puT555 .rough-node .label text,#mermaid-svg-cSmCHMuE86puT555 .node .label text,#mermaid-svg-cSmCHMuE86puT555 .image-shape .label,#mermaid-svg-cSmCHMuE86puT555 .icon-shape .label{text-anchor:middle;}#mermaid-svg-cSmCHMuE86puT555 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cSmCHMuE86puT555 .rough-node .label,#mermaid-svg-cSmCHMuE86puT555 .node .label,#mermaid-svg-cSmCHMuE86puT555 .image-shape .label,#mermaid-svg-cSmCHMuE86puT555 .icon-shape .label{text-align:center;}#mermaid-svg-cSmCHMuE86puT555 .node.clickable{cursor:pointer;}#mermaid-svg-cSmCHMuE86puT555 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cSmCHMuE86puT555 .arrowheadPath{fill:#333333;}#mermaid-svg-cSmCHMuE86puT555 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cSmCHMuE86puT555 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cSmCHMuE86puT555 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cSmCHMuE86puT555 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cSmCHMuE86puT555 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cSmCHMuE86puT555 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cSmCHMuE86puT555 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cSmCHMuE86puT555 .cluster text{fill:#333;}#mermaid-svg-cSmCHMuE86puT555 .cluster span{color:#333;}#mermaid-svg-cSmCHMuE86puT555 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cSmCHMuE86puT555 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cSmCHMuE86puT555 rect.text{fill:none;stroke-width:0;}#mermaid-svg-cSmCHMuE86puT555 .icon-shape,#mermaid-svg-cSmCHMuE86puT555 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cSmCHMuE86puT555 .icon-shape p,#mermaid-svg-cSmCHMuE86puT555 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cSmCHMuE86puT555 .icon-shape .label rect,#mermaid-svg-cSmCHMuE86puT555 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cSmCHMuE86puT555 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cSmCHMuE86puT555 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cSmCHMuE86puT555 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 0
1 + 无 default
1 + default
≥2 或 2+default
select 语句
case 数量?
block() 永久阻塞
直接 channel 操作

chansend/chanrecv
selectnbsend/selectnbrecv

非阻塞操作
selectgo 通用路径


3 selectgo 完整源码逐行解析

源码位置:runtime/select.go:116

3.1 scase 结构体

go 复制代码
type scase struct {
    c    *hchan         // ★ 关联的 channel
    elem unsafe.Pointer // ★ 数据元素指针
                          // 发送 case: 指向待发送的值
                          // 接收 case: 指向接收变量的地址
}
复制代码
scase 内存布局 (16 bytes on 64-bit)
┌─────────────────────────────────┐
│  c     *hchan      (8B)         │ → channel 指针
├─────────────────────────────────┤
│  elem  unsafe.P    (8B)         │ → 数据指针
└─────────────────────────────────┘

编译器分配的 scase 数组(栈上):
  case 0: recv  → scase{c: ch1, elem: &v}
  case 1: send  → scase{c: ch2, elem: &val}
  case 2: recv  → scase{c: ch3, elem: nil}  // v, ok:= <-ch3 的 ok 分支
  default: 不在 scase 数组中(dflt 单独处理)

3.2 selectgo 参数与返回值

go 复制代码
func selectgo(
    cas0    *scase,     // scase 数组首地址
    order0  *uint16,    // order 数组首地址(2*ncases 大小)
    pc0     *uintptr,   // race detector PC 数组
    nsends  int,        // 发送 case 数量
    nrecvs  int,        // 接收 case 数量
    block   bool,       // 是否阻塞(无 default 时 block=true)
) (int, bool)           // 返回: (选中的 case 索引, 接收是否成功)

关键设计:case 编排顺序

复制代码
scase 数组布局:
  [0]       [1]       ... [nsends-1]  [nsends]      ... [ncases-1]
  send0     send1     ... send_last   recv0         ... recv_last
  ↑─────── nsends ──────────↑↑──────── nrecvs ───────────────↑

发送 case 排在前面,接收 case 排在后面
这样 casi < nsends 表示是发送 case,否则是接收 case

3.3 Phase 0: 初始化与 pollorder 随机化

现在我们进入了运行时的核心。selectgo 函数首先做初始化工作。它从输入的 scase 数组中创建两个辅助数组------pollorder 和 lockorder。pollorder 是通过 fastrandn 随机打乱的,决定了下一次遍历 case 的顺序,这样保证了公平性,没有任何 channel 会被饿死。lockorder 则是按照 channel 的地址进行排序的,这样就保证了多个 goroutine 同时对同一批 channel 加锁的时候锁定顺序是一致的,从根本上避免了死锁的可能。这里有一个很重要的细节,你可以看到代码中使用了一个 for 循环配合 fastrandn 来做 Fisher-Yates 洗牌算法,每次循环随机交换两个元素的位置。初始化完毕后,sel lock 会按 lockorder 的顺序依次锁定所有 channel,然后程序进入主循环。

go 复制代码
func selectgo(...) (int, bool) {
    // 解析参数
    ncases := nsends + nrecvs
    scases := cas1[:ncases:ncases]
    pollorder := order1[:ncases:ncases]       // 前半段: 轮询顺序
    lockorder := order1[ncases:][:ncases:ncases]  // 后半段: 加锁顺序

    // ★★★ 生成随机轮询顺序(Fisher-Yates 洗牌算法)★★★
    norder := 0
    for i := range scases {
        cas := &scases[i]
        if cas.c == nil {
            cas.elem = nil    // nil channel → 跳过
            continue          // 不加入 pollorder 和 lockorder
        }
        // ★ 随机插入位置 j
        j := cheaprandn(uint32(norder + 1))
        pollorder[norder] = pollorder[j]  // 移动 j 位置的值到末尾
        pollorder[j] = uint16(i)          // 新值插入到 j 位置
        norder++
    }
    pollorder = pollorder[:norder]
    // 结果: pollorder 是 [0,1,2,...,norder-1] 的随机排列
    // 例: ncases=4, pollorder 可能是 [2, 0, 3, 1]

Fisher-Yates 洗牌过程图示

复制代码
初始: pollorder = []

i=0: j=random(1)=0  → pollorder = [0]
i=1: j=random(2)=1  → pollorder = [0, 1]
i=2: j=random(3)=0  → pollorder = [2, 1, 0]   ← 0 被移到末尾,2 插入位置 0
i=3: j=random(4)=2  → pollorder = [2, 1, 3, 0] ← 0 被移到末尾,3 插入位置 2

最终轮询顺序: 2 → 1 → 3 → 0
★ 这保证了多个 case 同时就绪时,被选中的概率均等

3.4 Phase 0 续: lockorder 排序

go 复制代码
    // ★★★ 按 channel 地址排序 lockorder(堆排序)★★★
    // 原因: 防止死锁!
    //   如果两个 G 同时 select 多个 channel,
    //   加锁顺序不一致可能导致死锁
    //   按地址排序保证了所有 G 的加锁顺序一致

    // 堆排序建立最大堆
    for i := range lockorder {
        j := i
        c := scases[pollorder[i]].c
        for j > 0 && scases[lockorder[(j-1)/2]].c.sortkey() < c.sortkey() {
            k := (j - 1) / 2
            lockorder[j] = lockorder[k]
            j = k
        }
        lockorder[j] = pollorder[i]
    }
    // 堆排序提取
    for i := len(lockorder) - 1; i >= 0; i-- {
        // ... 标准堆排序 ...
    }
    // 结果: lockorder 按 channel 地址从小到大排列

#mermaid-svg-b9dgK2DTB8TGXLAC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b9dgK2DTB8TGXLAC .error-icon{fill:#552222;}#mermaid-svg-b9dgK2DTB8TGXLAC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b9dgK2DTB8TGXLAC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b9dgK2DTB8TGXLAC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b9dgK2DTB8TGXLAC .marker.cross{stroke:#333333;}#mermaid-svg-b9dgK2DTB8TGXLAC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b9dgK2DTB8TGXLAC p{margin:0;}#mermaid-svg-b9dgK2DTB8TGXLAC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-b9dgK2DTB8TGXLAC .cluster-label text{fill:#333;}#mermaid-svg-b9dgK2DTB8TGXLAC .cluster-label span{color:#333;}#mermaid-svg-b9dgK2DTB8TGXLAC .cluster-label span p{background-color:transparent;}#mermaid-svg-b9dgK2DTB8TGXLAC .label text,#mermaid-svg-b9dgK2DTB8TGXLAC span{fill:#333;color:#333;}#mermaid-svg-b9dgK2DTB8TGXLAC .node rect,#mermaid-svg-b9dgK2DTB8TGXLAC .node circle,#mermaid-svg-b9dgK2DTB8TGXLAC .node ellipse,#mermaid-svg-b9dgK2DTB8TGXLAC .node polygon,#mermaid-svg-b9dgK2DTB8TGXLAC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b9dgK2DTB8TGXLAC .rough-node .label text,#mermaid-svg-b9dgK2DTB8TGXLAC .node .label text,#mermaid-svg-b9dgK2DTB8TGXLAC .image-shape .label,#mermaid-svg-b9dgK2DTB8TGXLAC .icon-shape .label{text-anchor:middle;}#mermaid-svg-b9dgK2DTB8TGXLAC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-b9dgK2DTB8TGXLAC .rough-node .label,#mermaid-svg-b9dgK2DTB8TGXLAC .node .label,#mermaid-svg-b9dgK2DTB8TGXLAC .image-shape .label,#mermaid-svg-b9dgK2DTB8TGXLAC .icon-shape .label{text-align:center;}#mermaid-svg-b9dgK2DTB8TGXLAC .node.clickable{cursor:pointer;}#mermaid-svg-b9dgK2DTB8TGXLAC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-b9dgK2DTB8TGXLAC .arrowheadPath{fill:#333333;}#mermaid-svg-b9dgK2DTB8TGXLAC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b9dgK2DTB8TGXLAC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b9dgK2DTB8TGXLAC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b9dgK2DTB8TGXLAC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-b9dgK2DTB8TGXLAC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b9dgK2DTB8TGXLAC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-b9dgK2DTB8TGXLAC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b9dgK2DTB8TGXLAC .cluster text{fill:#333;}#mermaid-svg-b9dgK2DTB8TGXLAC .cluster span{color:#333;}#mermaid-svg-b9dgK2DTB8TGXLAC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b9dgK2DTB8TGXLAC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-b9dgK2DTB8TGXLAC rect.text{fill:none;stroke-width:0;}#mermaid-svg-b9dgK2DTB8TGXLAC .icon-shape,#mermaid-svg-b9dgK2DTB8TGXLAC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b9dgK2DTB8TGXLAC .icon-shape p,#mermaid-svg-b9dgK2DTB8TGXLAC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-b9dgK2DTB8TGXLAC .icon-shape .label rect,#mermaid-svg-b9dgK2DTB8TGXLAC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b9dgK2DTB8TGXLAC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-b9dgK2DTB8TGXLAC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-b9dgK2DTB8TGXLAC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} lockorder (排序)
0 → 2 → 1 → 3

ch0.addr < ch2.addr < ch1.addr < ch3.addr

决定加锁顺序
pollorder (随机)
2 → 1 → 3 → 0

决定检查顺序

为什么需要两种顺序?

顺序 作用 生成方式
pollorder 决定检查 case 的顺序 → 保证公平性 随机洗牌
lockorder 决定加锁 channel 的顺序 → 防止死锁 按地址排序

3.5 sellock:按 lockorder 依次加锁

go 复制代码
func sellock(scases []scase, lockorder []uint16) {
    var c *hchan
    for _, o := range lockorder {
        c0 := scases[o].c
        if c0 != c {         // ★ 同一个 channel 不重复加锁
            c = c0
            lock(&c.lock)
        }
    }
}

// ★★★ 防死锁原理 ★★★
// 假设:
//   G1: select { case ch1: ... case ch2: ... }
//   G2: select { case ch2: ... case ch1: ... }
//
// 不排序:
//   G1 先锁 ch1, G2 先锁 ch2 → 死锁!
//
// lockorder 排序后(ch1.addr < ch2.addr):
//   G1 和 G2 都先锁 ch1, 再锁 ch2 → 不会死锁 ✅

主循环是 select 的核心,分为三个阶段。

  • 第一阶段,按照 pollorder 的顺序遍历所有 case,检查每个 channel 的状态。对于接收操作,如果发送等待队列里有 goroutine 在等待,就跳到 recv 标签;如果缓冲区有数据,就跳到 bufrecv 标签;如果 channel 已经关闭了,就跳到 rclose 标签。对于发送操作,如果 channel 已经关闭,直接跳到 sclose 标签会触发 panic;如果接收等待队列里有 goroutine 在等待,就跳到 send 标签;如果缓冲区有空位,就跳到 bufsend 标签。如果没有任何 case 就绪,就进入第二阶段。
  • 第二阶段,为每一个 case 创建一个 sudog 结构体,这个结构体代表了当前 goroutine 的等待状态。sudog 会被加入到对应 channel 的发送或者接收等待队列中,所有的 sudog 会串成一个链表挂在当前 goroutine 的 waiting 字段上。然后调用 gopark 把当前 goroutine 挂起。
  • 第三阶段,当 goroutine 被唤醒的时候,它从 gp.param 中取回 sudog,然后遍历 lockorder 找到匹配的 case,清理其他 channel 上未使用的 sudog,释放不再需要的结构体,最后跳转到对应的操作标签执行数据的收发。

3.6 Pass 1: 按随机顺序检查是否有 case 就绪

go 复制代码
    // ★★★ Pass 1: 按 pollorder 检查 ★★★
    for _, casei := range pollorder {
        casi = int(casei)
        cas = &scases[casi]
        c = cas.c

        if casi >= nsends {
            // ──── 接收 case ────
            
            // 检查1: 有等待的发送者?
            sg = c.sendq.dequeue()
            if sg != nil { goto recv }
            
            // 检查2: 缓冲区有数据?
            if c.qcount > 0 { goto bufrecv }
            
            // 检查3: 通道已关闭?
            if c.closed != 0 { goto rclose }
            
        } else {
            // ──── 发送 case ────
            
            // 检查1: 通道已关闭?(发送到已关闭通道 → panic)
            if c.closed != 0 { goto sclose }
            
            // 检查2: 有等待的接收者?
            sg = c.recvq.dequeue()
            if sg != nil { goto send }
            
            // 检查3: 缓冲区有空间?
            if c.qcount < c.dataqsiz { goto bufsend }
        }
    }

    // Pass 1 没有找到就绪的 case
    if !block {
        selunlock(scases, lockorder)
        casi = -1
        goto retc              // 有 default → 返回 -1
    }

#mermaid-svg-GV53xh7yXmIpXDMO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GV53xh7yXmIpXDMO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GV53xh7yXmIpXDMO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GV53xh7yXmIpXDMO .error-icon{fill:#552222;}#mermaid-svg-GV53xh7yXmIpXDMO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GV53xh7yXmIpXDMO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GV53xh7yXmIpXDMO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GV53xh7yXmIpXDMO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GV53xh7yXmIpXDMO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GV53xh7yXmIpXDMO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GV53xh7yXmIpXDMO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GV53xh7yXmIpXDMO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GV53xh7yXmIpXDMO .marker.cross{stroke:#333333;}#mermaid-svg-GV53xh7yXmIpXDMO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GV53xh7yXmIpXDMO p{margin:0;}#mermaid-svg-GV53xh7yXmIpXDMO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GV53xh7yXmIpXDMO .cluster-label text{fill:#333;}#mermaid-svg-GV53xh7yXmIpXDMO .cluster-label span{color:#333;}#mermaid-svg-GV53xh7yXmIpXDMO .cluster-label span p{background-color:transparent;}#mermaid-svg-GV53xh7yXmIpXDMO .label text,#mermaid-svg-GV53xh7yXmIpXDMO span{fill:#333;color:#333;}#mermaid-svg-GV53xh7yXmIpXDMO .node rect,#mermaid-svg-GV53xh7yXmIpXDMO .node circle,#mermaid-svg-GV53xh7yXmIpXDMO .node ellipse,#mermaid-svg-GV53xh7yXmIpXDMO .node polygon,#mermaid-svg-GV53xh7yXmIpXDMO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GV53xh7yXmIpXDMO .rough-node .label text,#mermaid-svg-GV53xh7yXmIpXDMO .node .label text,#mermaid-svg-GV53xh7yXmIpXDMO .image-shape .label,#mermaid-svg-GV53xh7yXmIpXDMO .icon-shape .label{text-anchor:middle;}#mermaid-svg-GV53xh7yXmIpXDMO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GV53xh7yXmIpXDMO .rough-node .label,#mermaid-svg-GV53xh7yXmIpXDMO .node .label,#mermaid-svg-GV53xh7yXmIpXDMO .image-shape .label,#mermaid-svg-GV53xh7yXmIpXDMO .icon-shape .label{text-align:center;}#mermaid-svg-GV53xh7yXmIpXDMO .node.clickable{cursor:pointer;}#mermaid-svg-GV53xh7yXmIpXDMO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GV53xh7yXmIpXDMO .arrowheadPath{fill:#333333;}#mermaid-svg-GV53xh7yXmIpXDMO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GV53xh7yXmIpXDMO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GV53xh7yXmIpXDMO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GV53xh7yXmIpXDMO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GV53xh7yXmIpXDMO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GV53xh7yXmIpXDMO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GV53xh7yXmIpXDMO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GV53xh7yXmIpXDMO .cluster text{fill:#333;}#mermaid-svg-GV53xh7yXmIpXDMO .cluster span{color:#333;}#mermaid-svg-GV53xh7yXmIpXDMO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GV53xh7yXmIpXDMO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GV53xh7yXmIpXDMO rect.text{fill:none;stroke-width:0;}#mermaid-svg-GV53xh7yXmIpXDMO .icon-shape,#mermaid-svg-GV53xh7yXmIpXDMO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GV53xh7yXmIpXDMO .icon-shape p,#mermaid-svg-GV53xh7yXmIpXDMO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GV53xh7yXmIpXDMO .icon-shape .label rect,#mermaid-svg-GV53xh7yXmIpXDMO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GV53xh7yXmIpXDMO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GV53xh7yXmIpXDMO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GV53xh7yXmIpXDMO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接收 case
Yes
No
Yes
No
Yes
No
发送 case
Yes
No
Yes
No
Yes
No
Pass 1: 按 pollorder 检查每个 case
casi >= nsends?

(接收 vs 发送)
sendq 有等待者?
goto recv

直接从发送者接收
qcount > 0?
goto bufrecv

从缓冲区接收
closed != 0?
goto rclose

通道已关闭
此 case 未就绪
closed != 0?
goto sclose

panic!
recvq 有等待者?
goto send

直接发给接收者
qcount < dataqsiz?
goto bufsend

写入缓冲区
检查下一个 case

3.7 Pass 2: 注册到所有 channel 的等待队列

go 复制代码
    // ★★★ Pass 2: 没有 case 就绪 + 需要阻塞 → 注册等待 ★★★
    nextp = &gp.waiting
    for _, casei := range lockorder {    // ★ 按 lockorder(非 pollorder)遍历
        casi = int(casei)
        cas = &scases[casi]
        c = cas.c

        sg := acquireSudog()            // 从池中获取 sudog
        sg.g = gp
        sg.isSelect = true              // ★ 标记来自 select
        sg.elem.set(cas.elem)
        sg.c.set(c)

        // 构建 gp.waiting 链表(按 lockorder 排列)
        *nextp = sg
        nextp = &sg.waitlink

        if casi < nsends {
            c.sendq.enqueue(sg)         // 发送 case → 加入 sendq
        } else {
            c.recvq.enqueue(sg)         // 接收 case → 加入 recvq
        }
    }

    // ★ 挂起当前 goroutine
    gp.parkingOnChan.Store(true)
    gopark(selparkcommit, nil, waitReason, traceBlockSelect, 1)
    // ★★★ 此时当前 G 完全停止,等待某个 channel 操作唤醒它 ★★★

selparkcommit------挂起前的解锁

go 复制代码
func selparkcommit(gp *g, _ unsafe.Pointer) bool {
    gp.activeStackChans = true
    gp.parkingOnChan.Store(false)

    // ★★★ 在挂起前释放所有 channel 锁 ★★★
    // 必须在 gopark 内部解锁,否则:
    //   1. 其他 G 无法操作这些 channel
    //   2. 可能死锁(持有锁 + 挂起)
    var lastc *hchan
    for sg := gp.waiting; sg != nil; sg = sg.waitlink {
        if sg.c.get() != lastc && lastc != nil {
            unlock(&lastc.lock)     // 按链表顺序解锁
        }
        lastc = sg.c.get()
    }
    if lastc != nil {
        unlock(&lastc.lock)
    }
    return true
}

3.8 被唤醒后的 Pass 3: 清理未成功的注册

go 复制代码
    // ★ 被某个 channel 操作唤醒 ★
    gp.activeStackChans = false
    
    // ★★★ 重新加锁所有 channel ★★★
    sellock(scases, lockorder)

    // 找到唤醒我们的那个 sudog
    sg = (*sudog)(gp.param)

    // ★★★ Pass 3: 从所有其他 channel 的等待队列中注销 ★★★
    casi = -1
    cas = nil
    caseSuccess = false
    sglist = gp.waiting

    // 先清空所有 elem(帮助 GC)
    for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink {
        sg1.isSelect = false
        sg1.elem.set(nil)
        sg1.c.set(nil)
    }
    gp.waiting = nil

    // ★ 遍历所有注册的 sudog
    for _, casei := range lockorder {
        k = &scases[casei]
        if sg == sglist {
            // ★ 这就是唤醒我们的那个 sudog → 记录选中 case
            casi = int(casei)
            cas = k
            caseSuccess = sglist.success
        } else {
            // ★ 不是唤醒我们的 → 从等待队列中移除
            c = k.c
            if int(casei) < nsends {
                c.sendq.dequeueSudoG(sglist)
            } else {
                c.recvq.dequeueSudoG(sglist)
            }
        }
        sgnext = sglist.waitlink
        sglist.waitlink = nil
        releaseSudog(sglist)     // 归还到池
        sglist = sgnext
    }

其他 Goroutine Channel 2 Channel 1 当前 Goroutine 其他 Goroutine Channel 2 Channel 1 当前 Goroutine #mermaid-svg-1Gcf2Q6x0hi5vrft{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1Gcf2Q6x0hi5vrft .error-icon{fill:#552222;}#mermaid-svg-1Gcf2Q6x0hi5vrft .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1Gcf2Q6x0hi5vrft .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1Gcf2Q6x0hi5vrft .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1Gcf2Q6x0hi5vrft .marker.cross{stroke:#333333;}#mermaid-svg-1Gcf2Q6x0hi5vrft svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1Gcf2Q6x0hi5vrft p{margin:0;}#mermaid-svg-1Gcf2Q6x0hi5vrft .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Gcf2Q6x0hi5vrft text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-1Gcf2Q6x0hi5vrft .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1Gcf2Q6x0hi5vrft .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-1Gcf2Q6x0hi5vrft .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-1Gcf2Q6x0hi5vrft .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-1Gcf2Q6x0hi5vrft #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-1Gcf2Q6x0hi5vrft .sequenceNumber{fill:white;}#mermaid-svg-1Gcf2Q6x0hi5vrft #sequencenumber{fill:#333;}#mermaid-svg-1Gcf2Q6x0hi5vrft #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-1Gcf2Q6x0hi5vrft .messageText{fill:#333;stroke:none;}#mermaid-svg-1Gcf2Q6x0hi5vrft .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Gcf2Q6x0hi5vrft .labelText,#mermaid-svg-1Gcf2Q6x0hi5vrft .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-1Gcf2Q6x0hi5vrft .loopText,#mermaid-svg-1Gcf2Q6x0hi5vrft .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-1Gcf2Q6x0hi5vrft .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1Gcf2Q6x0hi5vrft .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-1Gcf2Q6x0hi5vrft .noteText,#mermaid-svg-1Gcf2Q6x0hi5vrft .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-1Gcf2Q6x0hi5vrft .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Gcf2Q6x0hi5vrft .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Gcf2Q6x0hi5vrft .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Gcf2Q6x0hi5vrft .actorPopupMenu{position:absolute;}#mermaid-svg-1Gcf2Q6x0hi5vrft .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-1Gcf2Q6x0hi5vrft .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Gcf2Q6x0hi5vrft .actor-man circle,#mermaid-svg-1Gcf2Q6x0hi5vrft line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-1Gcf2Q6x0hi5vrft :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Pass 1: 检查就绪 → 无注册 sudog 到 recvq注册 sudog 到 sendqgopark 挂起向 Ch1 发送数据唤醒(通过 send 直接拷贝)被唤醒,重新加锁Pass 3: 找到唤醒者(Ch1)从 sendq 移除 sudog执行 Ch1 对应的 case

3.9 就绪 case 的具体执行路径

recv------从等待的发送者直接接收

go 复制代码
recv:
    // 可以从沉睡的发送者接收(同步接收)
    recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
    recvOK = true
    goto retc
    // recv 内部:
    //   无缓冲通道: recvDirect → 直接从发送者栈拷贝到接收者
    //   有缓冲通道: 从缓冲区头部取 + 发送者数据放入尾部
    //   然后 goready 唤醒发送者

bufrecv------从缓冲区接收

go 复制代码
bufrecv:
    recvOK = true
    qp = chanbuf(c, c.recvx)                    // 缓冲区当前读取位置
    if cas.elem != nil {
        typedmemmove(c.elemtype, cas.elem, qp)  // 拷贝到接收变量
    }
    typedmemclr(c.elemtype, qp)                 // 清空缓冲区 slot
    c.recvx++
    if c.recvx == c.dataqsiz { c.recvx = 0 }    // 环形回绕
    c.qcount--
    selunlock(scases, lockorder)
    goto retc

send------直接发给等待的接收者

go 复制代码
send:
    send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
    goto retc
    // send 内部:
    //   sendDirect → 直接从发送者栈拷贝到接收者栈
    //   goready 唤醒接收者

bufsend------写入缓冲区

go 复制代码
bufsend:
    typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem)  // 写入缓冲区
    c.sendx++
    if c.sendx == c.dataqsiz { c.sendx = 0 }
    c.qcount++
    selunlock(scases, lockorder)
    goto retc

rclose------从已关闭通道接收

go 复制代码
rclose:
    selunlock(scases, lockorder)
    recvOK = false                         // ★ received=false
    if cas.elem != nil {
        typedmemclr(c.elemtype, cas.elem)  // 零值
    }
    goto retc

sclose------向已关闭通道发送

go 复制代码
sclose:
    selunlock(scases, lockorder)
    panic(plainError("send on closed channel"))  // ★ panic!

4 selectgo 完整流程总图

#mermaid-svg-VrrinswLxp2nRObF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VrrinswLxp2nRObF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VrrinswLxp2nRObF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VrrinswLxp2nRObF .error-icon{fill:#552222;}#mermaid-svg-VrrinswLxp2nRObF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VrrinswLxp2nRObF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VrrinswLxp2nRObF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VrrinswLxp2nRObF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VrrinswLxp2nRObF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VrrinswLxp2nRObF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VrrinswLxp2nRObF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VrrinswLxp2nRObF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VrrinswLxp2nRObF .marker.cross{stroke:#333333;}#mermaid-svg-VrrinswLxp2nRObF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VrrinswLxp2nRObF p{margin:0;}#mermaid-svg-VrrinswLxp2nRObF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VrrinswLxp2nRObF .cluster-label text{fill:#333;}#mermaid-svg-VrrinswLxp2nRObF .cluster-label span{color:#333;}#mermaid-svg-VrrinswLxp2nRObF .cluster-label span p{background-color:transparent;}#mermaid-svg-VrrinswLxp2nRObF .label text,#mermaid-svg-VrrinswLxp2nRObF span{fill:#333;color:#333;}#mermaid-svg-VrrinswLxp2nRObF .node rect,#mermaid-svg-VrrinswLxp2nRObF .node circle,#mermaid-svg-VrrinswLxp2nRObF .node ellipse,#mermaid-svg-VrrinswLxp2nRObF .node polygon,#mermaid-svg-VrrinswLxp2nRObF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VrrinswLxp2nRObF .rough-node .label text,#mermaid-svg-VrrinswLxp2nRObF .node .label text,#mermaid-svg-VrrinswLxp2nRObF .image-shape .label,#mermaid-svg-VrrinswLxp2nRObF .icon-shape .label{text-anchor:middle;}#mermaid-svg-VrrinswLxp2nRObF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VrrinswLxp2nRObF .rough-node .label,#mermaid-svg-VrrinswLxp2nRObF .node .label,#mermaid-svg-VrrinswLxp2nRObF .image-shape .label,#mermaid-svg-VrrinswLxp2nRObF .icon-shape .label{text-align:center;}#mermaid-svg-VrrinswLxp2nRObF .node.clickable{cursor:pointer;}#mermaid-svg-VrrinswLxp2nRObF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VrrinswLxp2nRObF .arrowheadPath{fill:#333333;}#mermaid-svg-VrrinswLxp2nRObF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VrrinswLxp2nRObF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VrrinswLxp2nRObF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VrrinswLxp2nRObF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VrrinswLxp2nRObF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VrrinswLxp2nRObF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VrrinswLxp2nRObF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VrrinswLxp2nRObF .cluster text{fill:#333;}#mermaid-svg-VrrinswLxp2nRObF .cluster span{color:#333;}#mermaid-svg-VrrinswLxp2nRObF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VrrinswLxp2nRObF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VrrinswLxp2nRObF rect.text{fill:none;stroke-width:0;}#mermaid-svg-VrrinswLxp2nRObF .icon-shape,#mermaid-svg-VrrinswLxp2nRObF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VrrinswLxp2nRObF .icon-shape p,#mermaid-svg-VrrinswLxp2nRObF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VrrinswLxp2nRObF .icon-shape .label rect,#mermaid-svg-VrrinswLxp2nRObF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VrrinswLxp2nRObF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VrrinswLxp2nRObF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VrrinswLxp2nRObF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
false
true
selectgo 入口
Phase 0: 初始化
pollorder 随机洗牌

Fisher-Yates
lockorder 按 ch 地址排序

堆排序
sellock 按 lockorder 加锁
Pass 1: 按 pollorder 检查就绪
有 case 就绪?
执行就绪 case

解锁返回
block=true?

(无 default)
返回 casi=-1

(default)
Pass 2: 注册等待
为每个 case 创建 sudog

加入 channel 等待队列
gopark 挂起

selparkcommit 解锁
被唤醒
sellock 重新加锁
Pass 3: 清理注销
找到唤醒者 case
从其他 channel 移除 sudog
releaseSudog 归还
selunlock 解锁
返回 (casi, recvOK)


5 nil channel 在 select 中的行为

5.1 nil channel 永远不被选中

go 复制代码
var ch1 chan int   // nil
ch2 := make(chan int, 1)

select {
case v := <-ch1:     // ★ 永远不会被选中!
    fmt.Println(v)
case v := <-ch2:
    fmt.Println(v)   // 只会走这个
}

源码原因cas.c == nil 的 case 被跳过,不加入 pollorder 和 lockorder:

go 复制代码
for i := range scases {
    cas := &scases[i]
    if cas.c == nil {
        cas.elem = nil    // allow GC
        continue          // ★ 跳过!不加入任何顺序数组
    }
    // ... 加入 pollorder ...
}

5.2 利用 nil channel 禁用 select 分支

go 复制代码
func merge(ch1, ch2 <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for ch1 != nil || ch2 != nil {
            select {
            case v, ok := <-ch1:
                if !ok { ch1 = nil; continue }  // ★ 设为 nil 禁用此分支
                out <- v
            case v, ok := <-ch2:
                if !ok { ch2 = nil; continue }  // ★ 设为 nil 禁用此分支
                out <- v
            }
        }
    }()
    return out
}

#mermaid-svg-YV1IHhpolXVVEEDA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YV1IHhpolXVVEEDA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YV1IHhpolXVVEEDA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YV1IHhpolXVVEEDA .error-icon{fill:#552222;}#mermaid-svg-YV1IHhpolXVVEEDA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YV1IHhpolXVVEEDA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YV1IHhpolXVVEEDA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YV1IHhpolXVVEEDA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YV1IHhpolXVVEEDA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YV1IHhpolXVVEEDA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YV1IHhpolXVVEEDA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YV1IHhpolXVVEEDA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YV1IHhpolXVVEEDA .marker.cross{stroke:#333333;}#mermaid-svg-YV1IHhpolXVVEEDA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YV1IHhpolXVVEEDA p{margin:0;}#mermaid-svg-YV1IHhpolXVVEEDA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YV1IHhpolXVVEEDA .cluster-label text{fill:#333;}#mermaid-svg-YV1IHhpolXVVEEDA .cluster-label span{color:#333;}#mermaid-svg-YV1IHhpolXVVEEDA .cluster-label span p{background-color:transparent;}#mermaid-svg-YV1IHhpolXVVEEDA .label text,#mermaid-svg-YV1IHhpolXVVEEDA span{fill:#333;color:#333;}#mermaid-svg-YV1IHhpolXVVEEDA .node rect,#mermaid-svg-YV1IHhpolXVVEEDA .node circle,#mermaid-svg-YV1IHhpolXVVEEDA .node ellipse,#mermaid-svg-YV1IHhpolXVVEEDA .node polygon,#mermaid-svg-YV1IHhpolXVVEEDA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YV1IHhpolXVVEEDA .rough-node .label text,#mermaid-svg-YV1IHhpolXVVEEDA .node .label text,#mermaid-svg-YV1IHhpolXVVEEDA .image-shape .label,#mermaid-svg-YV1IHhpolXVVEEDA .icon-shape .label{text-anchor:middle;}#mermaid-svg-YV1IHhpolXVVEEDA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YV1IHhpolXVVEEDA .rough-node .label,#mermaid-svg-YV1IHhpolXVVEEDA .node .label,#mermaid-svg-YV1IHhpolXVVEEDA .image-shape .label,#mermaid-svg-YV1IHhpolXVVEEDA .icon-shape .label{text-align:center;}#mermaid-svg-YV1IHhpolXVVEEDA .node.clickable{cursor:pointer;}#mermaid-svg-YV1IHhpolXVVEEDA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YV1IHhpolXVVEEDA .arrowheadPath{fill:#333333;}#mermaid-svg-YV1IHhpolXVVEEDA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YV1IHhpolXVVEEDA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YV1IHhpolXVVEEDA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YV1IHhpolXVVEEDA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YV1IHhpolXVVEEDA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YV1IHhpolXVVEEDA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YV1IHhpolXVVEEDA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YV1IHhpolXVVEEDA .cluster text{fill:#333;}#mermaid-svg-YV1IHhpolXVVEEDA .cluster span{color:#333;}#mermaid-svg-YV1IHhpolXVVEEDA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YV1IHhpolXVVEEDA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YV1IHhpolXVVEEDA rect.text{fill:none;stroke-width:0;}#mermaid-svg-YV1IHhpolXVVEEDA .icon-shape,#mermaid-svg-YV1IHhpolXVVEEDA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YV1IHhpolXVVEEDA .icon-shape p,#mermaid-svg-YV1IHhpolXVVEEDA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YV1IHhpolXVVEEDA .icon-shape .label rect,#mermaid-svg-YV1IHhpolXVVEEDA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YV1IHhpolXVVEEDA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YV1IHhpolXVVEEDA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YV1IHhpolXVVEEDA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 都关闭后
ch1 关闭后
ch1 关闭前
ch1!=nil → 可选中
ch2!=nil → 可选中
ch1=nil → 永不选中
ch2!=nil → 唯一选项
ch1=nil → 退出
ch2=nil → 退出


6 select 的公平性保证

6.1 pollorder 随机化的必要性

go 复制代码
// 没有 pollorder 随机化时的问题:
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch1 <- 1
ch2 <- 2

select {
case v := <-ch1:   // 总是先检查 ch1
    fmt.Println(v)  // 永远输出 1,ch2 饥饿!
case v := <-ch2:
    fmt.Println(v)  // 永远不会执行
}

// 有 pollorder 随机化:
// 每次执行 select 时,检查顺序随机
// ch1 和 ch2 被选中的概率均等

6.2 isSelect 标志与 CAS 竞争

go 复制代码
// waitq.dequeue 中的特殊处理:
func (q *waitq) dequeue() *sudog {
    for {
        sgp := q.first
        // ...
        if sgp.isSelect {
            // ★ 来自 select 的 sudog 可能在多个 channel 的等待队列中
            // 使用 CAS 确保只有一个 channel 能唤醒它
            if !sgp.g.selectDone.CompareAndSwap(0, 1) {
                // 另一个 channel 已经赢得了竞争
                continue  // 跳过,尝试下一个
            }
        }
        return sgp
    }
}

场景

复制代码
一个 G 在 select 中同时注册到 ch1.recvq 和 ch2.recvq

如果 ch1 和 ch2 同时有数据可读:
  1. ch1 的发送者调用 send() → dequeue ch1.recvq 中的 sudog
     → selectDone.CAS(0,1) 成功 → 唤醒 G
  2. ch2 的发送者调用 send() → dequeue ch2.recvq 中的 sudog
     → selectDone.CAS(0,1) 失败(已经是1)→ 跳过
     → 继续找下一个等待者

★ CAS 确保一个 G 只被一个 channel 唤醒

7 完整使用示例合集

7.1 超时控制

go 复制代码
func doWithTimeout(ch <-chan int, timeout time.Duration) (int, error) {
    select {
    case v := <-ch:
        return v, nil
    case <-time.After(timeout):
        return 0, fmt.Errorf("timeout after %v", timeout)
    }
}

7.2 非阻塞发送/接收

go 复制代码
// 非阻塞接收
select {
case v := <-ch:
    fmt.Println("received:", v)
default:
    fmt.Println("no data available")
}

// 非阻塞发送
select {
case ch <- value:
    fmt.Println("sent")
default:
    fmt.Println("channel full, dropped")
}

7.3 优雅退出(done channel 模式)

go 复制代码
func worker(done <-chan struct{}, jobs <-chan int) {
    for {
        select {
        case <-done:
            fmt.Println("worker exiting")
            return
        case j, ok := <-jobs:
            if !ok {
                return  // jobs channel 已关闭
            }
            process(j)
        }
    }
}

7.4 定时器 + 退出

go 复制代码
func tickerDemo(done <-chan struct{}) {
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()
    
    for {
        select {
        case <-done:
            return
        case t := <-ticker.C:
            fmt.Println("tick at", t)
        }
    }
}

7.5 扇入(多 channel 合并)

go 复制代码
func fanIn(ch1, ch2 <-chan string) <-chan string {
    merged := make(chan string)
    go func() {
        defer close(merged)
        for ch1 != nil || ch2 != nil {
            select {
            case s, ok := <-ch1:
                if !ok { ch1 = nil; continue }
                merged <- s
            case s, ok := <-ch2:
                if !ok { ch2 = nil; continue }
                merged <- s
            }
        }
    }()
    return merged
}

8 经典问题与优化

8.1 select 中 for range 的空循环问题

go 复制代码
// ❌ 错误:空 select 导致 CPU 100%
for {
    select {
    case v := <-ch:
        process(v)
    default:
        // ★ 空的 default → 非阻塞 → 无限循环!CPU 飙升!
    }
}

// ✅ 正确:无 default → 阻塞等待
for {
    select {
    case v := <-ch:
        process(v)
    }
}

// ✅ 或添加 sleep
for {
    select {
    case v := <-ch:
        process(v)
    default:
        time.Sleep(10 * time.Millisecond)  // 让出 CPU
    }
}

8.2 select 死锁场景

go 复制代码
// ❌ 死锁1: 空 select
select {}   // 永久阻塞,没有任何 goroutine 能唤醒

// ❌ 死锁2: 循环等待
// G1: select { case <-ch2: ... case ch1<-: ... }  等 ch2 才发 ch1
// G2: select { case <-ch1: ... case ch2<-: ... }  等 ch1 才发 ch2
// ★ 虽然 lockorder 防止了 channel 锁死锁,但逻辑层面仍可能死锁

// ✅ 使用 context 打破循环
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
select {
case <-ctx.Done():
    return ctx.Err()
case v := <-ch:
    process(v)
}

8.3 time.After 内存泄漏

go 复制代码
// ❌ 泄漏:每次循环创建新 timer
for {
    select {
    case v := <-ch:
        process(v)
    case <-time.After(5 * time.Second):
        // ★ 每次 select 都创建新 timer
        // 如果 ch 频繁有数据,timer 不会被 GC 直到触发
        fmt.Println("timeout")
    }
}

// ✅ 修复:复用 timer
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
for {
    select {
    case v := <-ch:
        process(v)
        if !timer.Stop() {
            <-timer.C  // 排空 channel
        }
        timer.Reset(5 * time.Second)
    case <-timer.C:
        fmt.Println("timeout")
        timer.Reset(5 * time.Second)
    }
}

8.4 select case 数量限制

go 复制代码
// ★ 最多 65536 个 case(uint16 索引限制)
// 实际上超过几十个 case 就应该重构代码

// ❌ 过多 case
select {
case <-ch1: ...
case <-ch2: ...
// ... 1000 个 case ...
}

// ✅ 重构:使用 reflect.Select 或 map 分发

9 reflect.Select 动态 select

go 复制代码
// reflect.Select 允许运行时动态构建 select
cases := []reflect.SelectCase{
    {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch1)},
    {Dir: reflect.SelectSend, Chan: reflect.ValueOf(ch2), Send: reflect.ValueOf(42)},
    {Dir: reflect.SelectDefault},
}

chosen, value, ok := reflect.Select(cases)
// chosen: 选中 case 的索引
// value: 接收到的值
// ok: 接收是否成功

底层调用

go 复制代码
// reflect.rselect → runtime.reflect_rselect
func reflect_rselect(cases []runtimeSelect) (int, bool) {
    if len(cases) == 0 { block() }
    // 构建 scase 数组
    // 调用 selectgo
    // 返回选中的索引
}

10 总结:Select 核心知识图谱

#mermaid-svg-3Jn2Js5fhppC1UQs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3Jn2Js5fhppC1UQs .error-icon{fill:#552222;}#mermaid-svg-3Jn2Js5fhppC1UQs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3Jn2Js5fhppC1UQs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3Jn2Js5fhppC1UQs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3Jn2Js5fhppC1UQs .marker.cross{stroke:#333333;}#mermaid-svg-3Jn2Js5fhppC1UQs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3Jn2Js5fhppC1UQs p{margin:0;}#mermaid-svg-3Jn2Js5fhppC1UQs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3Jn2Js5fhppC1UQs .cluster-label text{fill:#333;}#mermaid-svg-3Jn2Js5fhppC1UQs .cluster-label span{color:#333;}#mermaid-svg-3Jn2Js5fhppC1UQs .cluster-label span p{background-color:transparent;}#mermaid-svg-3Jn2Js5fhppC1UQs .label text,#mermaid-svg-3Jn2Js5fhppC1UQs span{fill:#333;color:#333;}#mermaid-svg-3Jn2Js5fhppC1UQs .node rect,#mermaid-svg-3Jn2Js5fhppC1UQs .node circle,#mermaid-svg-3Jn2Js5fhppC1UQs .node ellipse,#mermaid-svg-3Jn2Js5fhppC1UQs .node polygon,#mermaid-svg-3Jn2Js5fhppC1UQs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3Jn2Js5fhppC1UQs .rough-node .label text,#mermaid-svg-3Jn2Js5fhppC1UQs .node .label text,#mermaid-svg-3Jn2Js5fhppC1UQs .image-shape .label,#mermaid-svg-3Jn2Js5fhppC1UQs .icon-shape .label{text-anchor:middle;}#mermaid-svg-3Jn2Js5fhppC1UQs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3Jn2Js5fhppC1UQs .rough-node .label,#mermaid-svg-3Jn2Js5fhppC1UQs .node .label,#mermaid-svg-3Jn2Js5fhppC1UQs .image-shape .label,#mermaid-svg-3Jn2Js5fhppC1UQs .icon-shape .label{text-align:center;}#mermaid-svg-3Jn2Js5fhppC1UQs .node.clickable{cursor:pointer;}#mermaid-svg-3Jn2Js5fhppC1UQs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3Jn2Js5fhppC1UQs .arrowheadPath{fill:#333333;}#mermaid-svg-3Jn2Js5fhppC1UQs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3Jn2Js5fhppC1UQs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3Jn2Js5fhppC1UQs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3Jn2Js5fhppC1UQs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3Jn2Js5fhppC1UQs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3Jn2Js5fhppC1UQs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3Jn2Js5fhppC1UQs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3Jn2Js5fhppC1UQs .cluster text{fill:#333;}#mermaid-svg-3Jn2Js5fhppC1UQs .cluster span{color:#333;}#mermaid-svg-3Jn2Js5fhppC1UQs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3Jn2Js5fhppC1UQs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3Jn2Js5fhppC1UQs rect.text{fill:none;stroke-width:0;}#mermaid-svg-3Jn2Js5fhppC1UQs .icon-shape,#mermaid-svg-3Jn2Js5fhppC1UQs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3Jn2Js5fhppC1UQs .icon-shape p,#mermaid-svg-3Jn2Js5fhppC1UQs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3Jn2Js5fhppC1UQs .icon-shape .label rect,#mermaid-svg-3Jn2Js5fhppC1UQs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3Jn2Js5fhppC1UQs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3Jn2Js5fhppC1UQs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3Jn2Js5fhppC1UQs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Select 核心知识
编译器三种优化

0case/1case/2case
selectgo 三阶段

Pass1/Pass2/Pass3
pollorder 随机化

Fisher-Yates 洗牌
lockorder 排序

按 ch 地址 防死锁
公平性保证

随机轮询顺序
nil channel

永不选中
isSelect + CAS

防重复唤醒
selparkcommit

挂起前解锁
常见陷阱

空default/time.After泄漏

selectgo 三阶段一图总结

复制代码
┌────────────────────────────────────────────────────────────┐
│                   selectgo 执行流程                         │
├──────────┬─────────────────────────────────────────────────┤
│ Phase 0  │ pollorder 随机洗牌 + lockorder 堆排序 + sellock │
├──────────┼─────────────────────────────────────────────────┤
│ Pass 1   │ 按 pollorder 检查就绪                            │
│          │ recv: sendq? → bufrecv? → closed?               │
│          │ send: closed? → recvq? → bufsend?               │
│          │ 有就绪 → 执行 → 返回                              │
│          │ 无就绪 + default → 返回 -1                       │
├──────────┼─────────────────────────────────────────────────┤
│ Pass 2   │ 无就绪 + 无 default → 注册等待                    │
│          │ 每个 case 创建 sudog → 加入 channel 等待队列       │
│          │ gopark 挂起 → selparkcommit 解锁                  │
├──────────┼─────────────────────────────────────────────────┤
│ Pass 3   │ 被唤醒 → sellock 重新加锁                         │
│          │ 找到唤醒者 sudog → 记录 casi                       │
│          │ 从其他 channel 移除 sudog → releaseSudog          │
│          │ selunlock → 返回 (casi, recvOK)                  │
└──────────┴─────────────────────────────────────────────────┘

源码索引

文件 关键内容
runtime/select.go:16 scase 结构体、sellock/selunlockselparkcommit
runtime/select.go:116 selectgo------三阶段核心实现
runtime/select.go:556 reflect_rselect------reflect 动态 select
runtime/select.go:624 dequeueSudoG------从等待队列移除指定 sudog
runtime/chan.go:159 chansend------channel 发送
runtime/chan.go:462 chanrecv------channel 接收
cmd/compile/internal/walk/select.go 编译器三种优化重写 + scase 类型定义
相关推荐
IT_陈寒2 小时前
React中useEffect依赖项这个坑我居然踩了三天
前端·人工智能·后端
AC赳赳老秦3 小时前
OpenClaw+Power Apps 实战:自动生成 Power Apps 应用、连接 Excel 数据源
大数据·开发语言·python·serverless·excel·deepseek·openclaw
提笔了无痕3 小时前
如何用Go实现整套RAG流程
开发语言·后端·golang
(Charon)3 小时前
【C++ 面试高频基础:指针、引用、const、static、new/delete 总结】
java·开发语言
成都第一深情IZZO3 小时前
事务未提交就发送 MQ,导致消费者读不到订单数据的问题
后端
wlsh154 小时前
Go 错误处理
golang
大橙子打游戏4 小时前
Fable5不能用了,但是依然能让 AI 纯靠截图玩通宝可梦
后端
Jason_chen4 小时前
Linux 3.0 总线机制与故障排查详解
后端
2601_961875244 小时前
法考考试时间安排及科目|时间表|资料已整理
开发语言·c#·inverted-index·suffix-tree·sstable·r-tree·lsm-tree