【Go 1.26.4】Golang Channel 深度解析

Golang Channel 深度解析

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

核心源文件:runtime/chan.goruntime/select.go


1 channel 功能完整介绍

1.1 语法定义与核心概念

Go 通道(channel)是 goroutine 间通信的核心原语,遵循 CSP(Communicating Sequential Processes) 模型:

"Don't communicate by sharing memory; share memory by communicating."

go 复制代码
// 声明(零值 nil)
var ch chan int               // 双向通道,nil
var chSend chan<- int         // 只写通道
var chRecv <-chan int         // 只读通道

// make 初始化
ch := make(chan int)          // 无缓冲通道(同步通道)
ch := make(chan int, 10)     // 有缓冲通道(异步通道),容量=10

// 操作
ch <- value                  // 发送(阻塞直到有人接收)
v := <-ch                    // 接收(阻塞直到有人发送)
v, ok := <-ch                // 接收 + 判断通道是否已关闭
close(ch)                    // 关闭通道
cap(ch)                      // 容量
len(ch)                      // 缓冲区中元素数量

1.2 三种通道类型对比

类型 定义 发送行为 接收行为 典型用途
无缓冲通道 make(chan T) 阻塞直到有接收者 阻塞直到有发送者 同步信号、握手
有缓冲通道 make(chan T, n) 缓冲区满时阻塞 缓冲区空时阻塞 生产者-消费者、限流
nil 通道 var ch chan T 永久阻塞 永久阻塞 select 中禁用分支

1.3 适用场景

#mermaid-svg-jDPBgpIHBV2PPHBt{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-jDPBgpIHBV2PPHBt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jDPBgpIHBV2PPHBt .error-icon{fill:#552222;}#mermaid-svg-jDPBgpIHBV2PPHBt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jDPBgpIHBV2PPHBt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jDPBgpIHBV2PPHBt .marker.cross{stroke:#333333;}#mermaid-svg-jDPBgpIHBV2PPHBt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jDPBgpIHBV2PPHBt p{margin:0;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge{stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .section--1 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section--1 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section--1 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section--1 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section--1 text{fill:#ffffff;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth--1{stroke-width:17;}#mermaid-svg-jDPBgpIHBV2PPHBt .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-0 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-0 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-0 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-0 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-0 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-0{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-0{stroke-width:14;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-1 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-1 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-1 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-1 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-1 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-1{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-1{stroke-width:11;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 text{fill:#ffffff;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-2{stroke-width:8;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-3 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-3 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-3 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-3 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-3 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-3{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-3{stroke-width:5;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-4 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-4 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-4 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-4 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-4 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-4{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-4{stroke-width:2;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-5 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-5 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-5 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-5 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-5 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-5{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-5{stroke-width:-1;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-6 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-6 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-6 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-6 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-6 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-6{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-6{stroke-width:-4;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-7 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-7 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-7 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-7 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-7 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-7{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-7{stroke-width:-7;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-8 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-8 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-8 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-8 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-8 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-8{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-8{stroke-width:-10;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-9 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-9 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-9 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-9 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-9 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-9{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-9{stroke-width:-13;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-10 rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-10 path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-10 circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-10 polygon,#mermaid-svg-jDPBgpIHBV2PPHBt .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-10 text{fill:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .node-icon-10{font-size:40px;color:black;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .edge-depth-10{stroke-width:-16;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled circle,#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:lightgray;}#mermaid-svg-jDPBgpIHBV2PPHBt .disabled text{fill:#efefef;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-root rect,#mermaid-svg-jDPBgpIHBV2PPHBt .section-root path,#mermaid-svg-jDPBgpIHBV2PPHBt .section-root circle,#mermaid-svg-jDPBgpIHBV2PPHBt .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-jDPBgpIHBV2PPHBt .section-root text{fill:#ffffff;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-root span{color:#ffffff;}#mermaid-svg-jDPBgpIHBV2PPHBt .section-2 span{color:#ffffff;}#mermaid-svg-jDPBgpIHBV2PPHBt .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-jDPBgpIHBV2PPHBt .edge{fill:none;}#mermaid-svg-jDPBgpIHBV2PPHBt .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-jDPBgpIHBV2PPHBt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Go Channel 适用场景
goroutine 通信
信号通知 done chan
数据传递 pipeline
并发控制
限流 buffered chan
等待组替代
生产者消费者
任务队列
结果收集
超时控制
select + time.After
context.Cancel
退出信号
close 通知所有接收者
done channel 模式


2 底层 runtime 实现原理

2.1 hchan 结构体完整字段说明

源码位置:runtime/chan.go:20

go 复制代码
type hchan struct {
    qcount   uint           // ★ 缓冲区中已有元素数量
                           //   len(ch) 直接读取此字段
                           //   仅对有缓冲通道有意义

    dataqsiz uint           // ★ 缓冲区容量(环形队列大小)
                           //   cap(ch) 直接读取此字段
                           //   无缓冲通道 = 0

    buf      unsafe.Pointer // ★ 指向环形队列缓冲区
                           //   dataqsiz * elemsize 大小的连续内存
                           //   无缓冲通道指向 hchan 自身的一个地址(raceaddr)
                           //   有缓冲通道指向独立分配的数组

    elemsize uint16         // 单个元素大小(字节)
    closed   uint32         // ★ 关闭标志:0=未关闭, 1=已关闭
    timer    *timer         // 关联的定时器(time.Tick/time.After 用)

    elemtype *_type         // ★ 元素类型元数据
                           //   用于 typedmemmove/typedmemclr
                           //   包含大小、对齐、GC 信息

    sendx    uint           // ★ 发送索引:下一个写入位置
                           //   范围 [0, dataqsiz)
                           //   写入后 sendx = (sendx+1) % dataqsiz

    recvx    uint           // ★ 接收索引:下一个读取位置
                           //   范围 [0, dataqsiz)
                           //   读取后 recvx = (recvx+1) % dataqsiz

    recvq    waitq          // ★ 等待接收的 goroutine 队列
                           //   缓冲区空时,接收者在此排队

    sendq    waitq          // ★ 等待发送的 goroutine 队列
                           //   缓冲区满时,发送者在此排队

    bubble   *synctestBubble // synctest 气泡(测试用)

    lock     mutex          // ★ 互斥锁
                           //   保护 hchan 所有字段
                           //   保护阻塞在此通道上的 sudog 字段
}

waitq 等待队列

go 复制代码
type waitq struct {
    first *sudog            // 队首
    last  *sudog            // 队尾
}

sudog(sudog = pseudo-G)

go 复制代码
type sudog struct {
    g     *g               // 关联的 goroutine
    elem  unsafe.Pointer   // 指向数据的指针(发送/接收的元素地址)
    next  *sudog           // 链表下一个
    prev  *sudog           // 链表上一个
    c     *hchan            // 关联的 channel
    success bool           // 操作是否成功
    isSelect  bool         // 是否来自 select
    waitlink  *sudog       // g.waiting 链表
    releasetime int64       // 阻塞分析时间戳
}

hchan 完整内存布局

复制代码
hchan 结构体 (96 bytes on 64-bit)
┌─────────────────────────────────────────────────────────┐
│  qcount    uint      (8B) │ 缓冲区元素数              │
├─────────────────────────────────────────────────────────┤
│  dataqsiz  uint      (8B) │ 缓冲区容量                │
├─────────────────────────────────────────────────────────┤
│  buf       unsafe.P  (8B) │ 环形队列指针 ──→ [slot0][slot1]...[slotN-1]│
├─────────────────────────────────────────────────────────┤
│  elemsize  uint16    (2B) │ 元素大小                  │
│  closed    uint32    (4B) │ 关闭标志                  │
├─────────────────────────────────────────────────────────┤
│  timer     *timer    (8B) │ 定时器                    │
├─────────────────────────────────────────────────────────┤
│  elemtype  *_type    (8B) │ 元素类型元数据            │
├─────────────────────────────────────────────────────────┤
│  sendx     uint      (8B) │ 发送索引                  │
├─────────────────────────────────────────────────────────┤
│  recvx     uint      (8B) │ 接收索引                  │
├─────────────────────────────────────────────────────────┤
│  recvq     waitq    (16B) │ 等待接收队列 ──→ sudog链表│
│           {first,last}    │                           │
├─────────────────────────────────────────────────────────┤
│  sendq     waitq    (16B) │ 等待发送队列 ──→ sudog链表│
│           {first,last}    │                           │
├─────────────────────────────────────────────────────────┤
│  bubble    *synctestB(8B) │ 测试气泡                  │
├─────────────────────────────────────────────────────────┤
│  lock      mutex     (8B) │ 互斥锁                    │
└─────────────────────────────────────────────────────────┘

2.2 环形队列缓冲区

有缓冲 channel 在底层使用 环形队列 (ring buffer) 存储数据。sendx/recvx 分别标记写入和读取位置。

复制代码
    dataqsiz = 6 (容量为 6 的环形缓冲区)

                      sendx (写入位置)
                          │
                          ▼
    ┌─────┬─────┬─────┬─────┬─────┬─────┐
    │  42 │  17 │  35 │     │     │     │
    └──▲──┴─────┴─────┴─────┴─────┴─────┘
       │
       └──── recvx (读取位置)

    当前状态: qcount = 3, 元素: [42, 17, 35]

    ── 写入一个元素 99 后 ──

                      sendx (向前移动, 取模 dataqsiz)
                          │
                          ▼
    ┌─────┬─────┬─────┬─────┬─────┬─────┐
    │  42 │  17 │  35 │  99 │     │     │
    └──▲──┴─────┴─────┴─────┴─────┴─────┘
       │
       └──── recvx (不变, 满时 = sendx)

    ── 读取一个元素后 ──

                    sendx
                      │
                      ▼
    ┌─────┬─────┬─────┬─────┬─────┬─────┐
    │     │  17 │  35 │  99 │     │     │
    └─────┴──▲──┴─────┴─────┴─────┴─────┘
             │
             └──── recvx (向前移动)

    关键公式:
    • 元素地址 = buf + recvx * elemsize   (读取)
    • 元素地址 = buf + sendx * elemsize   (写入)
    • recvx == sendx 且 qcount == 0  → 空
    • recvx == sendx 且 qcount > 0   → 满

#mermaid-svg-ySQxF8Ycy0HnaO7y{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-ySQxF8Ycy0HnaO7y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ySQxF8Ycy0HnaO7y .error-icon{fill:#552222;}#mermaid-svg-ySQxF8Ycy0HnaO7y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ySQxF8Ycy0HnaO7y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .marker.cross{stroke:#333333;}#mermaid-svg-ySQxF8Ycy0HnaO7y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ySQxF8Ycy0HnaO7y p{margin:0;}#mermaid-svg-ySQxF8Ycy0HnaO7y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .cluster-label text{fill:#333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .cluster-label span{color:#333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .cluster-label span p{background-color:transparent;}#mermaid-svg-ySQxF8Ycy0HnaO7y .label text,#mermaid-svg-ySQxF8Ycy0HnaO7y span{fill:#333;color:#333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .node rect,#mermaid-svg-ySQxF8Ycy0HnaO7y .node circle,#mermaid-svg-ySQxF8Ycy0HnaO7y .node ellipse,#mermaid-svg-ySQxF8Ycy0HnaO7y .node polygon,#mermaid-svg-ySQxF8Ycy0HnaO7y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ySQxF8Ycy0HnaO7y .rough-node .label text,#mermaid-svg-ySQxF8Ycy0HnaO7y .node .label text,#mermaid-svg-ySQxF8Ycy0HnaO7y .image-shape .label,#mermaid-svg-ySQxF8Ycy0HnaO7y .icon-shape .label{text-anchor:middle;}#mermaid-svg-ySQxF8Ycy0HnaO7y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ySQxF8Ycy0HnaO7y .rough-node .label,#mermaid-svg-ySQxF8Ycy0HnaO7y .node .label,#mermaid-svg-ySQxF8Ycy0HnaO7y .image-shape .label,#mermaid-svg-ySQxF8Ycy0HnaO7y .icon-shape .label{text-align:center;}#mermaid-svg-ySQxF8Ycy0HnaO7y .node.clickable{cursor:pointer;}#mermaid-svg-ySQxF8Ycy0HnaO7y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .arrowheadPath{fill:#333333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ySQxF8Ycy0HnaO7y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySQxF8Ycy0HnaO7y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ySQxF8Ycy0HnaO7y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySQxF8Ycy0HnaO7y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ySQxF8Ycy0HnaO7y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ySQxF8Ycy0HnaO7y .cluster text{fill:#333;}#mermaid-svg-ySQxF8Ycy0HnaO7y .cluster span{color:#333;}#mermaid-svg-ySQxF8Ycy0HnaO7y 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-ySQxF8Ycy0HnaO7y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ySQxF8Ycy0HnaO7y rect.text{fill:none;stroke-width:0;}#mermaid-svg-ySQxF8Ycy0HnaO7y .icon-shape,#mermaid-svg-ySQxF8Ycy0HnaO7y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySQxF8Ycy0HnaO7y .icon-shape p,#mermaid-svg-ySQxF8Ycy0HnaO7y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ySQxF8Ycy0HnaO7y .icon-shape .label rect,#mermaid-svg-ySQxF8Ycy0HnaO7y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySQxF8Ycy0HnaO7y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ySQxF8Ycy0HnaO7y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ySQxF8Ycy0HnaO7y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 状态判断
环形队列操作
qcount == dataqsiz
qcount == 0
发送: bufsendx = value

sendx = (sendx+1) % cap

qcount++
接收: value = bufrecvx

recvx = (recvx+1) % cap

qcount--
满: qcount == dataqsiz

发送阻塞 → sendq
空: qcount == 0

接收阻塞 → recvq

环形缓冲区的优势:避免频繁的内存分配和释放。数据直接在预分配的 buf 上读写,通过索引取模实现循环。当 dataqsiz 为 2 的幂时,取模优化为位运算 & (dataqsiz - 1)。

2.3 发送操作完整流程(chansend)

源码位置:runtime/chan.go:159

go 复制代码
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
    // ════════════════════════════════════════════
    // Step 0: nil channel 检查
    // ════════════════════════════════════════════
    if c == nil {
        if !block { return false }  // 非阻塞(select default)→ 返回 false
        gopark(nil, nil, waitReasonChanSendNilChan, traceBlockForever, 2)
        // ★ 向 nil 通道发送 = 永久阻塞!永远不会被唤醒
        throw("unreachable")
    }

    // ════════════════════════════════════════════
    // Step 1: 快速路径------非阻塞发送失败检测(不加锁)
    // ════════════════════════════════════════════
    if !block && c.closed == 0 && full(c) {
        return false
        // full() 检查:
        //   无缓冲: recvq.first == nil(没有等待的接收者)
        //   有缓冲: qcount == dataqsiz(缓冲区满)
    }

    lock(&c.lock)

    // ════════════════════════════════════════════
    // Step 2: 检查通道是否已关闭
    // ════════════════════════════════════════════
    if c.closed != 0 {
        unlock(&c.lock)
        panic(plainError("send on closed channel"))
        // ★ 向已关闭通道发送 → panic!
    }

    // ════════════════════════════════════════════
    // Step 3: 有等待的接收者?直接发送!
    // ════════════════════════════════════════════
    if sg := c.recvq.dequeue(); sg != nil {
        send(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true
        // ★ 绕过缓冲区,直接从发送者拷贝到接收者栈!
        // 这是无缓冲通道和有缓冲通道共用的快速路径
    }

    // ════════════════════════════════════════════
    // Step 4: 缓冲区有空间?写入缓冲区
    // ════════════════════════════════════════════
    if c.qcount < c.dataqsiz {
        qp := chanbuf(c, c.sendx)           // buf + sendx * elemsize
        typedmemmove(c.elemtype, qp, ep)    // 拷贝数据到缓冲区
        c.sendx++                            // 移动发送索引
        if c.sendx == c.dataqsiz {
            c.sendx = 0                      // 环形回绕
        }
        c.qcount++                           // 元素计数+1
        unlock(&c.lock)
        return true
    }

    // ════════════════════════════════════════════
    // Step 5: 非阻塞模式 + 缓冲区满 → 返回失败
    // ════════════════════════════════════════════
    if !block {
        unlock(&c.lock)
        return false
    }

    // ════════════════════════════════════════════
    // Step 6: 阻塞当前 goroutine
    // ════════════════════════════════════════════
    gp := getg()
    mysg := acquireSudog()            // 从 sudog 池获取
    mysg.elem.set(ep)                 // 记录发送数据的地址
    mysg.g = gp
    mysg.c.set(c)
    gp.waiting = mysg
    gp.param = nil
    c.sendq.enqueue(mysg)             // ★ 加入发送等待队列
    gp.parkingOnChan.Store(true)
    gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceBlockChanSend, 2)
    // ★ 当前 G 被挂起(parking),让出 CPU
    //   等待接收者来取数据后唤醒

    // ════════════════════════════════════════════
    // Step 7: 被唤醒后的清理
    // ════════════════════════════════════════════
    gp.waiting = nil
    gp.activeStackChans = false
    closed := !mysg.success
    mysg.c.set(nil)
    releaseSudog(mysg)                // 归还 sudog 到池
    if closed {
        panic(plainError("send on closed channel"))
    }
    return true
}

#mermaid-svg-c6gv8nQ5lz4DSJbd{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-c6gv8nQ5lz4DSJbd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c6gv8nQ5lz4DSJbd .error-icon{fill:#552222;}#mermaid-svg-c6gv8nQ5lz4DSJbd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c6gv8nQ5lz4DSJbd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .marker.cross{stroke:#333333;}#mermaid-svg-c6gv8nQ5lz4DSJbd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c6gv8nQ5lz4DSJbd p{margin:0;}#mermaid-svg-c6gv8nQ5lz4DSJbd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .cluster-label text{fill:#333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .cluster-label span{color:#333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .cluster-label span p{background-color:transparent;}#mermaid-svg-c6gv8nQ5lz4DSJbd .label text,#mermaid-svg-c6gv8nQ5lz4DSJbd span{fill:#333;color:#333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .node rect,#mermaid-svg-c6gv8nQ5lz4DSJbd .node circle,#mermaid-svg-c6gv8nQ5lz4DSJbd .node ellipse,#mermaid-svg-c6gv8nQ5lz4DSJbd .node polygon,#mermaid-svg-c6gv8nQ5lz4DSJbd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c6gv8nQ5lz4DSJbd .rough-node .label text,#mermaid-svg-c6gv8nQ5lz4DSJbd .node .label text,#mermaid-svg-c6gv8nQ5lz4DSJbd .image-shape .label,#mermaid-svg-c6gv8nQ5lz4DSJbd .icon-shape .label{text-anchor:middle;}#mermaid-svg-c6gv8nQ5lz4DSJbd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c6gv8nQ5lz4DSJbd .rough-node .label,#mermaid-svg-c6gv8nQ5lz4DSJbd .node .label,#mermaid-svg-c6gv8nQ5lz4DSJbd .image-shape .label,#mermaid-svg-c6gv8nQ5lz4DSJbd .icon-shape .label{text-align:center;}#mermaid-svg-c6gv8nQ5lz4DSJbd .node.clickable{cursor:pointer;}#mermaid-svg-c6gv8nQ5lz4DSJbd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .arrowheadPath{fill:#333333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c6gv8nQ5lz4DSJbd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c6gv8nQ5lz4DSJbd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c6gv8nQ5lz4DSJbd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c6gv8nQ5lz4DSJbd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c6gv8nQ5lz4DSJbd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c6gv8nQ5lz4DSJbd .cluster text{fill:#333;}#mermaid-svg-c6gv8nQ5lz4DSJbd .cluster span{color:#333;}#mermaid-svg-c6gv8nQ5lz4DSJbd 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-c6gv8nQ5lz4DSJbd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c6gv8nQ5lz4DSJbd rect.text{fill:none;stroke-width:0;}#mermaid-svg-c6gv8nQ5lz4DSJbd .icon-shape,#mermaid-svg-c6gv8nQ5lz4DSJbd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c6gv8nQ5lz4DSJbd .icon-shape p,#mermaid-svg-c6gv8nQ5lz4DSJbd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c6gv8nQ5lz4DSJbd .icon-shape .label rect,#mermaid-svg-c6gv8nQ5lz4DSJbd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c6gv8nQ5lz4DSJbd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c6gv8nQ5lz4DSJbd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c6gv8nQ5lz4DSJbd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
ch <- value

chansend()
c == nil?
永久阻塞 gopark

(永不唤醒)
快速路径:

非阻塞 && 未关闭 && 满?
return false
lock(&c.lock)
c.closed != 0?
panic!

send on closed channel
recvq 有等待者?
★ 直接发送

send(c, sg, ep)

绕过缓冲区

唤醒接收者
qcount < dataqsiz?
写入缓冲区

bufsendx = value

sendx++

qcount++
非阻塞?
return false
★ 阻塞

加入 sendq

gopark 挂起
被接收者唤醒

releaseSudog

return true

powershell 复制代码
三种路径:
    ┌─────────────────┬──────────────────┬───────────────────┐
    │ 直接发送         │ 写入缓冲区         │ 阻塞等待           │
    │ (recvq 非空)     │ (缓冲区有空位)     │ (缓冲区满)         │
    ├─────────────────┼──────────────────┼───────────────────┤
    │ 数据直接拷贝到    │ 数据拷贝到         │ goroutine 挂起     │
    │ 接收者的栈       │ 环形缓冲区          │ 等待接收者取走      │
    └─────────────────┴──────────────────┴───────────────────┘
2.3.1 直接发送 --- 有接收者等待

当发送时发现 recvq 非空(即有 goroutine 正阻塞等待接收),channel 走"直接发送"路径。

bash 复制代码
    场景: ch <- 42, 同时有 G2 在等待 <-ch

    发送前:

      hchan
      ┌─────────────────────────────┐
      │ recvq        │  sudog链表    │
      │              │ ┌──────┐     │          ┌──────────┐
      │              │ │ G2   │     │   elem──►│ G2 栈帧  │
      │              │ │ elem─┼─────┼─────────►│ (空)     │
      │              │ └──────┘     │          └──────────┘
      │ dataqsiz=0  │ (无缓冲)     │
      └─────────────────────────────┘

    发送: ch <- 42

      ┌──────────┐
      │ G1 栈帧  │     42  (值拷贝到 G2.elem 指向的地址)
      │ 42       │─────►
      └──────────┘       │
                         ▼
                    ┌──────────┐
                    │ G2 栈帧  │
                    │ 42 ← 写入  │
                    └──────────┘

    然后: goready(G2)  --- 将 G2 放回运行队列

sendDirect 将发送方的数据直接 memmove 到接收方 sudog.elem 所指向的地址(即接收方 goroutine 的栈上变量地址)。这个操作是 untyped memcpy,不经过中间缓冲区,因此性能很高。

2.4 接收操作完整流程(chanrecv)

源码位置:runtime/chan.go:462

go 复制代码
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
    // Step 0: nil channel → 永久阻塞
    if c == nil {
        if !block { return }
        gopark(nil, nil, waitReasonChanReceiveNilChan, traceBlockForever, 2)
        throw("unreachable")
    }

    // Step 1: 快速路径------非阻塞接收失败检测
    if !block && empty(c) {
        if atomic.Load(&c.closed) == 0 { return }
        // 通道已关闭且空 → 返回零值
        if empty(c) { return true, false }
    }

    lock(&c.lock)

    // Step 2: 通道已关闭 + 缓冲区空 → 返回零值
    if c.closed != 0 && c.qcount == 0 {
        unlock(&c.lock)
        if ep != nil { typedmemclr(c.elemtype, ep) }  // 清零
        return true, false   // selected=true, received=false
    }

    // Step 3: 有等待的发送者?直接接收!
    if sg := c.sendq.dequeue(); sg != nil {
        recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true, true
        // ★ 关键区别:
        //   无缓冲通道:直接从发送者拷贝到接收者
        //   有缓冲通道:从缓冲区头部读取,发送者数据放入缓冲区尾部
    }

    // Step 4: 缓冲区有数据?直接读取
    if c.qcount > 0 {
        qp := chanbuf(c, c.recvx)
        if ep != nil { typedmemmove(c.elemtype, ep, qp) }
        typedmemclr(c.elemtype, qp)    // 清空缓冲区 slot
        c.recvx++
        if c.recvx == c.dataqsiz { c.recvx = 0 }
        c.qcount--
        unlock(&c.lock)
        return true, true
    }

    // Step 5: 非阻塞 + 缓冲区空 → 返回失败
    if !block {
        unlock(&c.lock)
        return false, false
    }

    // Step 6: 阻塞当前 goroutine
    gp := getg()
    mysg := acquireSudog()
    mysg.elem.set(ep)
    mysg.g = gp
    mysg.c.set(c)
    c.recvq.enqueue(mysg)             // ★ 加入接收等待队列
    gopark(chanparkcommit, ...)

    // Step 7: 被唤醒后清理
    success := mysg.success
    releaseSudog(mysg)
    return true, success
}

recv 函数的关键逻辑------有缓冲通道满时的接收:

go 复制代码
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
    if c.dataqsiz == 0 {
        // 无缓冲:直接从发送者 → 接收者
        if ep != nil { recvDirect(c.elemtype, sg, ep) }
    } else {
        // 有缓冲(且满):
        //   1. 从缓冲区头部取出数据给接收者
        //   2. 发送者的数据放入缓冲区尾部
        //   → 因为缓冲区满,头和尾是同一个 slot!
        qp := chanbuf(c, c.recvx)
        if ep != nil { typedmemmove(c.elemtype, ep, qp) }    // 1. 取出头部
        typedmemmove(c.elemtype, qp, sg.elem.get())          // 2. 放入尾部
        c.recvx++
        if c.recvx == c.dataqsiz { c.recvx = 0 }
        c.sendx = c.recvx   // ★ 因为满,sendx 追上 recvx
    }
    sg.elem.set(nil)
    gp := sg.g
    unlockf()
    goready(gp, skip+1)   // ★ 唤醒发送者
}

#mermaid-svg-2Hmtikj4Yjw599UX{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-2Hmtikj4Yjw599UX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2Hmtikj4Yjw599UX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2Hmtikj4Yjw599UX .error-icon{fill:#552222;}#mermaid-svg-2Hmtikj4Yjw599UX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2Hmtikj4Yjw599UX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2Hmtikj4Yjw599UX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2Hmtikj4Yjw599UX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2Hmtikj4Yjw599UX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2Hmtikj4Yjw599UX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2Hmtikj4Yjw599UX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2Hmtikj4Yjw599UX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2Hmtikj4Yjw599UX .marker.cross{stroke:#333333;}#mermaid-svg-2Hmtikj4Yjw599UX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2Hmtikj4Yjw599UX p{margin:0;}#mermaid-svg-2Hmtikj4Yjw599UX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2Hmtikj4Yjw599UX .cluster-label text{fill:#333;}#mermaid-svg-2Hmtikj4Yjw599UX .cluster-label span{color:#333;}#mermaid-svg-2Hmtikj4Yjw599UX .cluster-label span p{background-color:transparent;}#mermaid-svg-2Hmtikj4Yjw599UX .label text,#mermaid-svg-2Hmtikj4Yjw599UX span{fill:#333;color:#333;}#mermaid-svg-2Hmtikj4Yjw599UX .node rect,#mermaid-svg-2Hmtikj4Yjw599UX .node circle,#mermaid-svg-2Hmtikj4Yjw599UX .node ellipse,#mermaid-svg-2Hmtikj4Yjw599UX .node polygon,#mermaid-svg-2Hmtikj4Yjw599UX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2Hmtikj4Yjw599UX .rough-node .label text,#mermaid-svg-2Hmtikj4Yjw599UX .node .label text,#mermaid-svg-2Hmtikj4Yjw599UX .image-shape .label,#mermaid-svg-2Hmtikj4Yjw599UX .icon-shape .label{text-anchor:middle;}#mermaid-svg-2Hmtikj4Yjw599UX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2Hmtikj4Yjw599UX .rough-node .label,#mermaid-svg-2Hmtikj4Yjw599UX .node .label,#mermaid-svg-2Hmtikj4Yjw599UX .image-shape .label,#mermaid-svg-2Hmtikj4Yjw599UX .icon-shape .label{text-align:center;}#mermaid-svg-2Hmtikj4Yjw599UX .node.clickable{cursor:pointer;}#mermaid-svg-2Hmtikj4Yjw599UX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2Hmtikj4Yjw599UX .arrowheadPath{fill:#333333;}#mermaid-svg-2Hmtikj4Yjw599UX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2Hmtikj4Yjw599UX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2Hmtikj4Yjw599UX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2Hmtikj4Yjw599UX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2Hmtikj4Yjw599UX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2Hmtikj4Yjw599UX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2Hmtikj4Yjw599UX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2Hmtikj4Yjw599UX .cluster text{fill:#333;}#mermaid-svg-2Hmtikj4Yjw599UX .cluster span{color:#333;}#mermaid-svg-2Hmtikj4Yjw599UX 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-2Hmtikj4Yjw599UX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2Hmtikj4Yjw599UX rect.text{fill:none;stroke-width:0;}#mermaid-svg-2Hmtikj4Yjw599UX .icon-shape,#mermaid-svg-2Hmtikj4Yjw599UX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2Hmtikj4Yjw599UX .icon-shape p,#mermaid-svg-2Hmtikj4Yjw599UX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2Hmtikj4Yjw599UX .icon-shape .label rect,#mermaid-svg-2Hmtikj4Yjw599UX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2Hmtikj4Yjw599UX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2Hmtikj4Yjw599UX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2Hmtikj4Yjw599UX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
<-ch

chanrecv()
c == nil?
永久阻塞
lock
closed && qcount==0?
返回零值

(true, false)
sendq 有等待者?
★ 直接接收

无缓冲: 发送者→接收者

有缓冲: 头部出+尾部入
qcount > 0?
读取缓冲区

bufrecvx

recvx++

qcount--
非阻塞?
return false
★ 阻塞

加入 recvq

gopark 挂起

2.5 close 操作完整流程(closechan)

源码位置:runtime/chan.go:383

go 复制代码
func closechan(c *hchan) {
    // nil channel → panic
    if c == nil { panic(plainError("close of nil channel")) }

    lock(&c.lock)
    // 已关闭 → panic
    if c.closed != 0 {
        unlock(&c.lock)
        panic(plainError("close of closed channel"))
    }

    c.closed = 1  // ★ 标记为已关闭

    var glist gList

    // ★ 释放所有等待的接收者
    for {
        sg := c.recvq.dequeue()
        if sg == nil { break }
        if sg.elem.get() != nil {
            typedmemclr(c.elemtype, sg.elem.get())  // 清零接收者的 elem
        }
        gp := sg.g
        gp.param = unsafe.Pointer(sg)
        sg.success = false   // ★ 标记:因为通道关闭而唤醒
        glist.push(gp)
    }

    // ★ 释放所有等待的发送者(他们会 panic)
    for {
        sg := c.sendq.dequeue()
        if sg == nil { break }
        sg.elem.set(nil)
        gp := sg.g
        gp.param = unsafe.Pointer(sg)
        sg.success = false
        glist.push(gp)       // 发送者唤醒后会 panic("send on closed channel")
    }
    unlock(&c.lock)

    // ★ 批量唤醒所有 goroutine(在锁外执行,避免死锁)
    for !glist.empty() {
        gp := glist.pop()
        goready(gp, 3)       // 将 G 加入运行队列
    }
}

close 语义总结
#mermaid-svg-8jfb4DF8dBDMqqHn{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-8jfb4DF8dBDMqqHn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8jfb4DF8dBDMqqHn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8jfb4DF8dBDMqqHn .error-icon{fill:#552222;}#mermaid-svg-8jfb4DF8dBDMqqHn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8jfb4DF8dBDMqqHn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8jfb4DF8dBDMqqHn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8jfb4DF8dBDMqqHn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8jfb4DF8dBDMqqHn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8jfb4DF8dBDMqqHn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8jfb4DF8dBDMqqHn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8jfb4DF8dBDMqqHn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8jfb4DF8dBDMqqHn .marker.cross{stroke:#333333;}#mermaid-svg-8jfb4DF8dBDMqqHn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8jfb4DF8dBDMqqHn p{margin:0;}#mermaid-svg-8jfb4DF8dBDMqqHn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8jfb4DF8dBDMqqHn .cluster-label text{fill:#333;}#mermaid-svg-8jfb4DF8dBDMqqHn .cluster-label span{color:#333;}#mermaid-svg-8jfb4DF8dBDMqqHn .cluster-label span p{background-color:transparent;}#mermaid-svg-8jfb4DF8dBDMqqHn .label text,#mermaid-svg-8jfb4DF8dBDMqqHn span{fill:#333;color:#333;}#mermaid-svg-8jfb4DF8dBDMqqHn .node rect,#mermaid-svg-8jfb4DF8dBDMqqHn .node circle,#mermaid-svg-8jfb4DF8dBDMqqHn .node ellipse,#mermaid-svg-8jfb4DF8dBDMqqHn .node polygon,#mermaid-svg-8jfb4DF8dBDMqqHn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8jfb4DF8dBDMqqHn .rough-node .label text,#mermaid-svg-8jfb4DF8dBDMqqHn .node .label text,#mermaid-svg-8jfb4DF8dBDMqqHn .image-shape .label,#mermaid-svg-8jfb4DF8dBDMqqHn .icon-shape .label{text-anchor:middle;}#mermaid-svg-8jfb4DF8dBDMqqHn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8jfb4DF8dBDMqqHn .rough-node .label,#mermaid-svg-8jfb4DF8dBDMqqHn .node .label,#mermaid-svg-8jfb4DF8dBDMqqHn .image-shape .label,#mermaid-svg-8jfb4DF8dBDMqqHn .icon-shape .label{text-align:center;}#mermaid-svg-8jfb4DF8dBDMqqHn .node.clickable{cursor:pointer;}#mermaid-svg-8jfb4DF8dBDMqqHn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8jfb4DF8dBDMqqHn .arrowheadPath{fill:#333333;}#mermaid-svg-8jfb4DF8dBDMqqHn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8jfb4DF8dBDMqqHn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8jfb4DF8dBDMqqHn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8jfb4DF8dBDMqqHn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8jfb4DF8dBDMqqHn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8jfb4DF8dBDMqqHn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8jfb4DF8dBDMqqHn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8jfb4DF8dBDMqqHn .cluster text{fill:#333;}#mermaid-svg-8jfb4DF8dBDMqqHn .cluster span{color:#333;}#mermaid-svg-8jfb4DF8dBDMqqHn 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-8jfb4DF8dBDMqqHn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8jfb4DF8dBDMqqHn rect.text{fill:none;stroke-width:0;}#mermaid-svg-8jfb4DF8dBDMqqHn .icon-shape,#mermaid-svg-8jfb4DF8dBDMqqHn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8jfb4DF8dBDMqqHn .icon-shape p,#mermaid-svg-8jfb4DF8dBDMqqHn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8jfb4DF8dBDMqqHn .icon-shape .label rect,#mermaid-svg-8jfb4DF8dBDMqqHn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8jfb4DF8dBDMqqHn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8jfb4DF8dBDMqqHn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8jfb4DF8dBDMqqHn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} close(ch) 后
仍然可以接收

直到缓冲区空
全部唤醒

返回零值
全部唤醒

然后 panic
立即 panic
缓冲区空后

返回零值
panic
缓冲区数据
接收返回 (value, false)
等待的接收者
received=false
等待的发送者
💥 send on closed
新发送
💥 send on closed
新接收
received=false
重复 close
💥 close of closed

2.6 makechan 内存分配策略

源码位置:runtime/chan.go:52

go 复制代码
func makechan(t *chantype, size int) *hchan {
    elem := t.Elem
    mem, overflow := math.MulUintptr(elem.Size_, uintptr(size))

    var c *hchan
    switch {
    case mem == 0:
        // ★ 无缓冲通道(size=0)或元素大小为0
        // 只分配 hchan 结构体,不分配缓冲区
        c = (*hchan)(mallocgc(hchanSize, nil, true))
        c.buf = c.raceaddr()  // buf 指向 hchan 内部一个地址(仅 race 用)

    case !elem.Pointers():
        // ★ 元素不含指针(如 int, float64)
        // 一次性分配 hchan + 缓冲区(连续内存,减少一次分配)
        c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
        c.buf = add(unsafe.Pointer(c), hchanSize)  // buf 紧跟 hchan 后面

    default:
        // ★ 元素含指针(如 *int, string, struct含指针)
        // 分开分配:hchan 和缓冲区各自独立
        // 因为 buf 中有指针,需要 GC 扫描
        c = new(hchan)
        c.buf = mallocgc(mem, elem, true)
    }

    c.elemsize = uint16(elem.Size_)
    c.elemtype = elem
    c.dataqsiz = uint(size)
    lockInit(&c.lock, lockRankHchan)
    return c
}
复制代码
三种内存分配方式:

1. 无缓冲通道 (size=0):
   ┌──────────────┐
   │   hchan      │  mallocgc(hchanSize)
   │   buf ──→ ──┤──→ 指向 hchan 自身内部
   └──────────────┘

2. 有缓冲 + 元素无指针 (如 chan int, cap=4):
   ┌──────────────┬───────┬───────┬───────┬───────┐
   │   hchan      │slot[0]│slot[1]│slot[2]│slot[3]│  一次分配
   │   buf ───────│───────│───────│───────│───────│
   └──────────────┴───────┴───────┴───────┴───────┘
   mallocgc(hchanSize + 4*8)

3. 有缓冲 + 元素有指针 (如 chan *int, cap=4):
   ┌──────────────┐       ┌───────┬───────┬───────┬───────┐
   │   hchan      │       │slot[0]│slot[1]│slot[2]│slot[3]│
   │   buf ─────────────→ │       │       │       │       │
   └──────────────┘       └───────┴───────┴───────┴───────┘
   new(hchan)              mallocgc(mem, elem, true)

2.7 直接发送 send()------跨 goroutine 栈拷贝

这是 Go 中唯一一个 goroutine 写另一个 goroutine 栈的场景:

go 复制代码
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
    if sg.elem.get() != nil {
        sendDirect(c.elemtype, sg, ep)
        // ★ 直接从发送者的栈 → 接收者的栈 拷贝数据!
        // 普通情况下 goroutine 只能写自己的栈
        // 这是唯一例外,因为此时接收者已 gopark,栈不会变化
    }
    gp := sg.g
    unlockf()
    gp.param = unsafe.Pointer(sg)
    sg.success = true
    goready(gp, skip+1)   // 唤醒接收者
}

func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
    dst := sg.elem.get()  // 接收者栈上的目标地址
    typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)
    memmove(dst, src, t.Size_)  // 跨栈内存拷贝
}

接收者 G (parked) hchan 发送者 G 接收者 G (parked) hchan 发送者 G #mermaid-svg-YK0nQdrGwJhXqx0j{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-YK0nQdrGwJhXqx0j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YK0nQdrGwJhXqx0j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YK0nQdrGwJhXqx0j .error-icon{fill:#552222;}#mermaid-svg-YK0nQdrGwJhXqx0j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YK0nQdrGwJhXqx0j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YK0nQdrGwJhXqx0j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YK0nQdrGwJhXqx0j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YK0nQdrGwJhXqx0j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YK0nQdrGwJhXqx0j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YK0nQdrGwJhXqx0j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YK0nQdrGwJhXqx0j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YK0nQdrGwJhXqx0j .marker.cross{stroke:#333333;}#mermaid-svg-YK0nQdrGwJhXqx0j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YK0nQdrGwJhXqx0j p{margin:0;}#mermaid-svg-YK0nQdrGwJhXqx0j .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YK0nQdrGwJhXqx0j text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-YK0nQdrGwJhXqx0j .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YK0nQdrGwJhXqx0j .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-YK0nQdrGwJhXqx0j .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-YK0nQdrGwJhXqx0j .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-YK0nQdrGwJhXqx0j #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-YK0nQdrGwJhXqx0j .sequenceNumber{fill:white;}#mermaid-svg-YK0nQdrGwJhXqx0j #sequencenumber{fill:#333;}#mermaid-svg-YK0nQdrGwJhXqx0j #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-YK0nQdrGwJhXqx0j .messageText{fill:#333;stroke:none;}#mermaid-svg-YK0nQdrGwJhXqx0j .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YK0nQdrGwJhXqx0j .labelText,#mermaid-svg-YK0nQdrGwJhXqx0j .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-YK0nQdrGwJhXqx0j .loopText,#mermaid-svg-YK0nQdrGwJhXqx0j .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-YK0nQdrGwJhXqx0j .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-YK0nQdrGwJhXqx0j .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-YK0nQdrGwJhXqx0j .noteText,#mermaid-svg-YK0nQdrGwJhXqx0j .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-YK0nQdrGwJhXqx0j .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YK0nQdrGwJhXqx0j .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YK0nQdrGwJhXqx0j .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YK0nQdrGwJhXqx0j .actorPopupMenu{position:absolute;}#mermaid-svg-YK0nQdrGwJhXqx0j .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-YK0nQdrGwJhXqx0j .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YK0nQdrGwJhXqx0j .actor-man circle,#mermaid-svg-YK0nQdrGwJhXqx0j line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-YK0nQdrGwJhXqx0j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} memmove(dst=R的栈, src=S的栈, size) lockrecvq.dequeue() → 找到接收者sendDirect: 直接写入接收者栈!unlockgoready(R) → 唤醒接收者从 gopark 返回,数据已在栈上

2.8 hchan 不变量(核心约束)

源码注释中明确了以下不变量:

复制代码
1. c.sendq 和 c.recvq 至少一个为空
   (例外:无缓冲通道 + select 中同时发送和接收)

2. 有缓冲通道:
   - qcount > 0 → recvq 为空(有数据可读,不需等待)
   - qcount < dataqsiz → sendq 为空(有空间可写,不需等待)

3. 关闭后:
   - 不能再发送(panic)
   - 缓冲区数据仍可接收
   - 缓冲区空后接收返回零值

3 示例代码逐行解析

3.1 无缓冲通道------同步握手

go 复制代码
ch := make(chan int)
// [运行时] makechan(t, 0)
//   dataqsiz = 0, buf = raceaddr
//   sendq = {nil, nil}, recvq = {nil, nil}

// ── goroutine A: 发送 ──
go func() {
    ch <- 42
    // [运行时] chansend(c, &42, true, pc):
    //   1. c != nil → 继续
    //   2. full(c) → dataqsiz==0 && recvq.first==nil → true(没有等待接收者)
    //   3. lock(&c.lock)
    //   4. c.closed == 0 → 继续
    //   5. recvq.dequeue() → nil(没有等待接收者)
    //   6. qcount(0) < dataqsiz(0)? → false
    //   7. block=true → 阻塞
    //      acquireSudog → enqueue to sendq → gopark
    //      ★ goroutine A 挂起,等待接收者
    
    // 之后 goroutine B 接收时:
    //   recvq 没有,sendq 有 A
    //   recv(c, sg_A, ep, unlockf, 3):
    //     dataqsiz == 0 → recvDirect: 直接从 A 的栈拷贝到 B 的栈
    //     goready(A) → 唤醒 A
    //   A 从 gopark 返回 → return true
}()

// ── goroutine B: 接收 ──
v := <-ch
// [运行时] chanrecv(c, &v, true):
//   如果 A 先到且已阻塞在 sendq:
//     1. lock
//     2. sendq.dequeue() → 找到 A
//     3. recv(c, sg_A, &v, unlockf):
//        dataqsiz==0 → recvDirect: 直接从 A 的栈 → B 的栈
//     4. goready(A) → 唤醒 A
//     5. return true, true

//   如果 B 先到(A 还没发送):
//     1. lock
//     2. sendq.dequeue() → nil
//     3. qcount(0) == 0 → 缓冲区空
//     4. 阻塞:加入 recvq → gopark
//     5. A 后发送时:chansend 发现 recvq 有 B → send 直接传递

3.2 有缓冲通道------异步收发

go 复制代码
ch := make(chan int, 3)
// [运行时] makechan(t, 3)
//   dataqsiz = 3, buf = mallocgc(3*8, int类型, false)
//   因为 int 无指针,一次分配 hchan + buf

ch <- 10  // 发送1
// [运行时] chansend:
//   recvq 为空 → 不走直接发送
//   qcount(0) < dataqsiz(3) → 写入缓冲区
//     buf[sendx=0] = 10, sendx=1, qcount=1
//   return true ✅ 不阻塞

ch <- 20  // 发送2
//   buf[sendx=1] = 20, sendx=2, qcount=2

ch <- 30  // 发送3
//   buf[sendx=2] = 30, sendx=0 (回绕), qcount=3
//   ★ 缓冲区满!

ch <- 40  // 发送4 → 阻塞
// [运行时] chansend:
//   recvq 为空, qcount(3) == dataqsiz(3) → 缓冲区满
//   block=true → 阻塞,加入 sendq

v1 := <-ch  // 接收1
// [运行时] chanrecv:
//   sendq 有等待者 → recv(c, sg, &v1, unlockf):
//     dataqsiz > 0:
//       qp = buf[recvx=0] = 10
//       v1 = 10                    ← 从缓冲区取出
//       buf[0] = sg.elem(40)       ← 发送者数据放入
//       recvx = 1, sendx = 1
//   goready(发送者) → 唤醒
//   ★ 接收者拿到缓冲区头部,发送者数据补到尾部

3.3 close 逐行解析

go 复制代码
close(ch)
// [运行时] closechan(c):
//   1. c == nil → panic("close of nil channel")
//   2. lock(&c.lock)
//   3. c.closed != 0 → panic("close of closed channel")
//   4. c.closed = 1
//   5. 释放 recvq 所有等待者:sg.success = false, goready
//   6. 释放 sendq 所有等待者:sg.success = false, goready
//      (发送者唤醒后检查 !mysg.success → panic)
//   7. unlock
//   8. 批量 goready 所有被释放的 G

// close 后的接收
v, ok := <-ch
// [运行时] chanrecv:
//   如果缓冲区还有数据 → 继续读取,ok=true
//   如果缓冲区空 → typedmemclr(零值), return (true, false)
//                                      selected=true, received=false

// close 后的发送
ch <- 1
// [运行时] chansend:
//   c.closed != 0 → panic("send on closed channel") 💥

4 完整使用示例合集

4.1 基础收发完整可运行代码

go 复制代码
package main

import "fmt"

func main() {
    // 无缓冲通道------同步通信
    done := make(chan struct{})
    go func() {
        fmt.Println("working...")
        done <- struct{}{} // 发送完成信号
    }()
    <-done // 等待完成
    fmt.Println("done")

    // 有缓冲通道------异步通信
    ch := make(chan string, 2)
    ch <- "hello"     // 不阻塞(缓冲区有空间)
    ch <- "world"     // 不阻塞(缓冲区刚好满)
    fmt.Println(<-ch) // hello
    fmt.Println(<-ch) // world
}

4.2 单向通道与通道方向约束

go 复制代码
func producer(ch chan<- int) {  // 只写通道
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch <-chan int) {  // 只读通道
    for v := range ch {
        fmt.Println(v)
    }
}

func main() {
    ch := make(chan int, 5)
    go producer(ch)   // 双向→只写,隐式转换
    consumer(ch)      // 双向→只读,隐式转换
}

4.3 select 多路复用

go 复制代码
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()
    go func() {
        time.Sleep(50 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // select 随机选择一个就绪的 case
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    case msg := <-ch2:
        fmt.Println(msg)  // 大概率先执行这个(ch2 更快)
    }
}

4.4 超时模式

go 复制代码
func main() {
    ch := make(chan int)
    go func() {
        time.Sleep(2 * time.Second)
        ch <- 42
    }()

    select {
    case v := <-ch:
        fmt.Println("received:", v)
    case <-time.After(1 * time.Second):
        fmt.Println("timeout!")  // 1秒超时
    }
}

4.5 done channel 模式

go 复制代码
func main() {
    done := make(chan struct{})

    go func() {
        defer close(done) // ★ 函数退出时关闭,通知所有等待者
        // ... 长时间工作 ...
        time.Sleep(100 * time.Millisecond)
    }()

    <-done // 等待工作完成
    fmt.Println("goroutine finished")
}

4.6 扇出扇入模式(fan-out/fan-in)

go 复制代码
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("worker %d: job %d\n", id, j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)

    // 扇出:3 个 worker 消费同一个 jobs 通道
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // 发送任务
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)

    // 扇入:收集所有结果
    for r := 1; r <= 9; r++ {
        fmt.Println("result:", <-results)
    }
}

5 并发安全专题详解

5.1 channel 本身的并发安全性

channel 操作本身是并发安全的------hchan 有 mutex 保护:

go 复制代码
// 所有操作都需要获取锁
lock(&c.lock)
// ... 修改 hchan 字段 ...
unlock(&c.lock)

但 channel 不保证外部数据的并发安全

go 复制代码
// ✅ channel 操作安全
ch <- value     // 安全
v := <-ch       // 安全

// ❌ 通过 channel 传递的共享数据可能不安全
m := map[string]int{}
ch <- &m        // 传递了 map 指针
// 如果两个 goroutine 同时修改 m → data race!

5.2 goroutine 泄漏的常见模式

go 复制代码
// ❌ 泄漏模式1:无人接收
func leak1() {
    ch := make(chan int)
    go func() {
        ch <- 42  // 永远阻塞!没有人接收
    }()
    // 函数返回,ch 失去引用
    // 但 goroutine 仍然阻塞在 ch 上 → 泄漏
}

// ✅ 修复:使用 select + context
func noLeak1(ctx context.Context) {
    ch := make(chan int)
    go func() {
        select {
        case ch <- 42:
        case <-ctx.Done():  // 超时或取消时退出
        }
    }()
}

// ❌ 泄漏模式2:无人发送
func leak2() {
    ch := make(chan int)
    go func() {
        v := <-ch  // 永远阻塞!没有人发送
        fmt.Println(v)
    }()
}

// ✅ 修复:close 或 select
func noLeak2() {
    ch := make(chan int)
    go func() {
        select {
        case v := <-ch:
            fmt.Println(v)
        case <-time.After(5 * time.Second):
            fmt.Println("timeout")
        }
    }()
}

6 经典问题与优化

6.1 遍历中删除/新增元素的风险

channel 的 for range 语义:

go 复制代码
for v := range ch {
    // 循环直到 ch 被 close
    // close 是唯一退出 for range 的方式
}

// for range 编译为:
//   var hiter hiter
//   for mapiterinit(t, m, &hiter); hiter.key != nil; mapiternext(&hiter) { ... }
// channel 的 for range 编译为:
//   for {
//       v, ok := <-ch
//       if !ok { break }  // ch 已关闭且缓冲区空
//       // 使用 v
//   }

注意 :channel 不像 map,for range 期间不允许关闭以外的修改(发送是安全的,但 close 必须在发送方)。

6.2 channel key 类型限制

channel 本身可以作为 map 的 key:

go 复制代码
m := map[chan int]string{}  // ✅ chan 是引用类型,可比较(比较指针地址)
ch1 := make(chan int)
ch2 := make(chan int)
m[ch1] = "first"
m[ch2] = "second"
// ch1 == ch1 → true(同一指针)
// ch1 == ch2 → false(不同指针)

6.3 select 的公平性与随机化

selectgo 的实现保证了随机性:

go 复制代码
func selectgo(...) (int, bool) {
    // ★ pollorder 使用 Fisher-Yates 洗牌算法随机化
    for i := range scases {
        j := cheaprandn(uint32(norder + 1))
        pollorder[norder] = pollorder[j]
        pollorder[j] = uint16(i)
        norder++
    }
    
    // pass 1: 按随机顺序检查是否有 case 就绪
    for _, casei := range pollorder {
        // 检查此 case 是否可以立即执行
    }
    
    // pass 2: 如果没有就绪的 case,按 lockorder 加锁
    // lockorder 按 channel 地址排序(防止死锁)
    
    // pass 3: 如果仍然没有就绪,阻塞
}

公平性保证

  1. pollorder 随机化 → 多个就绪 case 被选中的概率均等
  2. lockorder 按地址排序 → 多个 channel 加锁顺序固定,防止死锁
  3. 没有 default → 阻塞等待;有 default → 非阻塞返回

6.4 内存优化与最佳实践

go 复制代码
// 1. 及时 close 不再使用的 channel(让接收者退出 for range)
close(ch)

// 2. 使用 buffered channel 减少阻塞
ch := make(chan int, runtime.NumCPU())  // 缓冲区大小 = CPU 核数

// 3. struct{} 零大小通道(纯信号,零内存开销)
done := make(chan struct{})
done <- struct{}{}
<-done

// 4. 使用 sync.Pool 复用 sudog(runtime 自动完成)

// 5. 避免 goroutine 泄漏------总是确保有退出路径

7 select 底层实现详解

7.1 selectgo 完整流程

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

go 复制代码
func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) {
    // ══════════════════════════════════════
    // Phase 1: 随机化 pollorder + 排序 lockorder
    // ══════════════════════════════════════
    // pollorder: 随机顺序(决定检查顺序,保证公平性)
    // lockorder: 按 channel 地址排序(决定加锁顺序,防止死锁)

    // ══════════════════════════════════════
    // Phase 2: sellock------按 lockorder 依次加锁
    // ══════════════════════════════════════
    sellock(scases, lockorder)

    // ══════════════════════════════════════
    // Phase 3: Pass 1------按 pollorder 检查是否有就绪 case
    // ══════════════════════════════════════
    for _, casei := range pollorder {
        casi = int(casei)
        cas = &scases[casi]
        c = cas.c

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

    // ══════════════════════════════════════
    // Phase 4: 没有 default → 注册到所有 channel 的等待队列
    // ══════════════════════════════════════
    // 为每个 case 创建 sudog,加入对应 channel 的 sendq/recvq
    // 然后 gopark 挂起当前 G

    // ══════════════════════════════════════
    // Phase 5: 被唤醒后,从所有其他 channel 的等待队列中移除
    // ══════════════════════════════════════
}

#mermaid-svg-pE9LetTpfBBGnHPE{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-pE9LetTpfBBGnHPE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pE9LetTpfBBGnHPE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pE9LetTpfBBGnHPE .error-icon{fill:#552222;}#mermaid-svg-pE9LetTpfBBGnHPE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pE9LetTpfBBGnHPE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pE9LetTpfBBGnHPE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pE9LetTpfBBGnHPE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pE9LetTpfBBGnHPE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pE9LetTpfBBGnHPE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pE9LetTpfBBGnHPE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pE9LetTpfBBGnHPE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pE9LetTpfBBGnHPE .marker.cross{stroke:#333333;}#mermaid-svg-pE9LetTpfBBGnHPE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pE9LetTpfBBGnHPE p{margin:0;}#mermaid-svg-pE9LetTpfBBGnHPE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pE9LetTpfBBGnHPE .cluster-label text{fill:#333;}#mermaid-svg-pE9LetTpfBBGnHPE .cluster-label span{color:#333;}#mermaid-svg-pE9LetTpfBBGnHPE .cluster-label span p{background-color:transparent;}#mermaid-svg-pE9LetTpfBBGnHPE .label text,#mermaid-svg-pE9LetTpfBBGnHPE span{fill:#333;color:#333;}#mermaid-svg-pE9LetTpfBBGnHPE .node rect,#mermaid-svg-pE9LetTpfBBGnHPE .node circle,#mermaid-svg-pE9LetTpfBBGnHPE .node ellipse,#mermaid-svg-pE9LetTpfBBGnHPE .node polygon,#mermaid-svg-pE9LetTpfBBGnHPE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pE9LetTpfBBGnHPE .rough-node .label text,#mermaid-svg-pE9LetTpfBBGnHPE .node .label text,#mermaid-svg-pE9LetTpfBBGnHPE .image-shape .label,#mermaid-svg-pE9LetTpfBBGnHPE .icon-shape .label{text-anchor:middle;}#mermaid-svg-pE9LetTpfBBGnHPE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pE9LetTpfBBGnHPE .rough-node .label,#mermaid-svg-pE9LetTpfBBGnHPE .node .label,#mermaid-svg-pE9LetTpfBBGnHPE .image-shape .label,#mermaid-svg-pE9LetTpfBBGnHPE .icon-shape .label{text-align:center;}#mermaid-svg-pE9LetTpfBBGnHPE .node.clickable{cursor:pointer;}#mermaid-svg-pE9LetTpfBBGnHPE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pE9LetTpfBBGnHPE .arrowheadPath{fill:#333333;}#mermaid-svg-pE9LetTpfBBGnHPE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pE9LetTpfBBGnHPE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pE9LetTpfBBGnHPE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pE9LetTpfBBGnHPE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pE9LetTpfBBGnHPE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pE9LetTpfBBGnHPE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pE9LetTpfBBGnHPE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pE9LetTpfBBGnHPE .cluster text{fill:#333;}#mermaid-svg-pE9LetTpfBBGnHPE .cluster span{color:#333;}#mermaid-svg-pE9LetTpfBBGnHPE 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-pE9LetTpfBBGnHPE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pE9LetTpfBBGnHPE rect.text{fill:none;stroke-width:0;}#mermaid-svg-pE9LetTpfBBGnHPE .icon-shape,#mermaid-svg-pE9LetTpfBBGnHPE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pE9LetTpfBBGnHPE .icon-shape p,#mermaid-svg-pE9LetTpfBBGnHPE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pE9LetTpfBBGnHPE .icon-shape .label rect,#mermaid-svg-pE9LetTpfBBGnHPE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pE9LetTpfBBGnHPE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pE9LetTpfBBGnHPE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pE9LetTpfBBGnHPE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
Yes
No
select { case ch1<-v: ... case <-ch2: ... default: ... }
编译器生成 selectgo 调用
Phase 1: 随机化 pollorder

排序 lockorder
Phase 2: sellock 按 lockorder 加锁
Phase 3: 按 pollorder 检查就绪
有 case 就绪?
执行就绪 case

解锁返回
有 default?
执行 default

解锁返回
注册到所有 channel 等待队列

gopark 挂起
被某个 channel 唤醒
从其他 channel 等待队列移除

执行对应 case

7.2 select 死锁预防

多个 channel 加锁顺序必须一致(按地址排序):

复制代码
假设两个 goroutine 同时 select 两个 channel:

G1: select { case chA<-: ... case <-chB: ... }
G2: select { case chB<-: ... case <-chA: ... }

如果不排序:
  G1 先锁 chA,G2 先锁 chB → 死锁!

lockorder 按 chA < chB 排序后:
  G1 和 G2 都先锁 chA → 不会死锁 ✅

8 总结:Channel 核心知识图谱

#mermaid-svg-VmI8g8gdaAaJE2VB{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-VmI8g8gdaAaJE2VB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VmI8g8gdaAaJE2VB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VmI8g8gdaAaJE2VB .error-icon{fill:#552222;}#mermaid-svg-VmI8g8gdaAaJE2VB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VmI8g8gdaAaJE2VB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VmI8g8gdaAaJE2VB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VmI8g8gdaAaJE2VB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VmI8g8gdaAaJE2VB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VmI8g8gdaAaJE2VB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VmI8g8gdaAaJE2VB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VmI8g8gdaAaJE2VB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VmI8g8gdaAaJE2VB .marker.cross{stroke:#333333;}#mermaid-svg-VmI8g8gdaAaJE2VB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VmI8g8gdaAaJE2VB p{margin:0;}#mermaid-svg-VmI8g8gdaAaJE2VB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VmI8g8gdaAaJE2VB .cluster-label text{fill:#333;}#mermaid-svg-VmI8g8gdaAaJE2VB .cluster-label span{color:#333;}#mermaid-svg-VmI8g8gdaAaJE2VB .cluster-label span p{background-color:transparent;}#mermaid-svg-VmI8g8gdaAaJE2VB .label text,#mermaid-svg-VmI8g8gdaAaJE2VB span{fill:#333;color:#333;}#mermaid-svg-VmI8g8gdaAaJE2VB .node rect,#mermaid-svg-VmI8g8gdaAaJE2VB .node circle,#mermaid-svg-VmI8g8gdaAaJE2VB .node ellipse,#mermaid-svg-VmI8g8gdaAaJE2VB .node polygon,#mermaid-svg-VmI8g8gdaAaJE2VB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VmI8g8gdaAaJE2VB .rough-node .label text,#mermaid-svg-VmI8g8gdaAaJE2VB .node .label text,#mermaid-svg-VmI8g8gdaAaJE2VB .image-shape .label,#mermaid-svg-VmI8g8gdaAaJE2VB .icon-shape .label{text-anchor:middle;}#mermaid-svg-VmI8g8gdaAaJE2VB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VmI8g8gdaAaJE2VB .rough-node .label,#mermaid-svg-VmI8g8gdaAaJE2VB .node .label,#mermaid-svg-VmI8g8gdaAaJE2VB .image-shape .label,#mermaid-svg-VmI8g8gdaAaJE2VB .icon-shape .label{text-align:center;}#mermaid-svg-VmI8g8gdaAaJE2VB .node.clickable{cursor:pointer;}#mermaid-svg-VmI8g8gdaAaJE2VB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VmI8g8gdaAaJE2VB .arrowheadPath{fill:#333333;}#mermaid-svg-VmI8g8gdaAaJE2VB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VmI8g8gdaAaJE2VB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VmI8g8gdaAaJE2VB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VmI8g8gdaAaJE2VB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VmI8g8gdaAaJE2VB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VmI8g8gdaAaJE2VB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VmI8g8gdaAaJE2VB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VmI8g8gdaAaJE2VB .cluster text{fill:#333;}#mermaid-svg-VmI8g8gdaAaJE2VB .cluster span{color:#333;}#mermaid-svg-VmI8g8gdaAaJE2VB 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-VmI8g8gdaAaJE2VB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VmI8g8gdaAaJE2VB rect.text{fill:none;stroke-width:0;}#mermaid-svg-VmI8g8gdaAaJE2VB .icon-shape,#mermaid-svg-VmI8g8gdaAaJE2VB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VmI8g8gdaAaJE2VB .icon-shape p,#mermaid-svg-VmI8g8gdaAaJE2VB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VmI8g8gdaAaJE2VB .icon-shape .label rect,#mermaid-svg-VmI8g8gdaAaJE2VB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VmI8g8gdaAaJE2VB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VmI8g8gdaAaJE2VB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VmI8g8gdaAaJE2VB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Channel 核心知识
三种类型

无缓冲/有缓冲/nil
hchan 结构体

环形队列+等待队列
发送流程

直接→缓冲→阻塞
接收流程

直接→缓冲→阻塞
close 语义

唤醒所有等待者
跨栈拷贝

sendDirect/recvDirect
select 随机化

pollorder+lockorder
并发安全

hchan.mutex 保护
goroutine 泄漏

无人接收/无人发送

hchan 关键不变量一图总结

复制代码
┌──────────────────────────────────────────────────────────┐
│                    hchan 状态机                            │
├────────────┬─────────────────┬───────────────────────────┤
│   状态      │   sendq          │   recvq                  │
├────────────┼─────────────────┼───────────────────────────┤
│ 缓冲区空    │ 空(可写入)     │ 有等待者(阻塞中)        │
│ 缓冲区部分  │ 空               │ 空                        │
│ 缓冲区满    │ 有等待者(阻塞中)│ 空(可读取)              │
│ 已关闭+空   │ 空(panic新发)  │ 空(返回零值)            │
│ 已关闭+有数据│ 空(panic新发)  │ 空(继续读缓冲区)        │
├────────────┼─────────────────┼───────────────────────────┤
│ nil 通道    │ 发送=永久阻塞    │ 接收=永久阻塞             │
└────────────┴─────────────────┴───────────────────────────┘

源码索引

文件 关键内容
runtime/chan.go:20 hchan 结构体、waitqmakechanchansendchanrecvclosechansendrecvsendDirectrecvDirect
runtime/select.go:116 selectgoscasesellock/selunlock、pollorder/lockorder
相关推荐
盈建云系统2 小时前
B2B产品展示网站怎么做?从产品目录到询盘表单,企业获客页面搭建流程
开发语言·网站搭建·开发网站
不会C语言的男孩2 小时前
Linux 系统编程 · 第 4 章:文件属性与元数据
linux·c语言·开发语言
kernelcraft2 小时前
Boto3:Python 操作 AWS 的官方 SDK
开发语言·python·其他·aws
D3bugRealm2 小时前
cryptography:Python 开发者的加密标准库
开发语言·python·其他
Rain5092 小时前
2.1 Nest.js 项目初始化与模块化架构
开发语言·前端·javascript·后端·架构·数据分析·node.js
cjp5602 小时前
009. ASP.NET WEB API 用户关联esp32设备
前端·后端·asp.net
贺国亚2 小时前
Text-to-SQL与Analytics-Agent
后端
小熊美家熊猫系统3 小时前
电子合同技术实现与合规实践
java·开发语言·分布式
ytttr8733 小时前
C# 定时数据库备份工具
开发语言·数据库·c#