Golang Channel 深度解析
基于 Go 1.26.4 源码,源码路径:
github.com/go-go1.26.4核心源文件:
runtime/chan.go、runtime/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: 如果仍然没有就绪,阻塞
}
公平性保证:
- pollorder 随机化 → 多个就绪 case 被选中的概率均等
- lockorder 按地址排序 → 多个 channel 加锁顺序固定,防止死锁
- 没有 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:20hchan结构体、waitq、makechan、chansend、chanrecv、closechan、send、recv、sendDirect、recvDirectruntime/select.go:116selectgo、scase、sellock/selunlock、pollorder/lockorder