无缓冲 channel 是同步通知的唯一可靠方式:用 make(chan int) 或 make(chan struct{}) 创建时,发送与接收必须同时就绪才完成,Go 运行时强制 goroutine 挂起与唤醒;而有缓冲 channel(如 make(chan bool, 1))因缓冲未满时不阻塞,无法保证同步可靠性。

无缓冲 channel 是同步通知的唯一可靠方式
用 make(chan int) 或 make(chan struct{}) 创建的无缓冲 channel,发送操作会一直阻塞,直到有 goroutine 在同一时刻执行接收;反之亦然。这不是“大概率同步”,而是运行时级的强制握手。
常见错误是拿 make(chan bool, 1) 当同步信号用:只要缓冲没满,ch 就立刻返回,接收方可能还没启动、已退出,或根本没读——信号就此丢失。
- 适用场景包括:goroutine 初始化就绪通知(如
ready := make(chan struct{}))、任务完成确认、错误中断广播(配合select+ctx.Done()) - 关键约束:必须确保接收端已就绪再发;否则发送操作永久阻塞,整个 goroutine 卡死
- 典型误写:
go func() { ch —— 这里发送在接收前就触发,但无法保证 goroutine 已调度到接收语句,极易死锁
有缓冲 channel 必须配合 close 才能安全异步解耦
缓冲 channel 的本质是暂存区,不是异步保险丝。只靠 make(chan *Request, 100) 不代表你就能放心发数据——若没人消费、不关 channel、也不设超时,生产者最终会堵死,或消费者永远 range 不退出。
真正起作用的是 “缓冲容量 + 明确关闭 + 消费者守约” 三者组合。
-
close(ch)必须由生产者或协调者调用,否则for range ch永远不会退出 - 缓冲大小选 1 往往不够:网络请求突发、批量写入等场景下容易溢出;应按峰值并发或 SLA 设定(如
make(chan []byte, 50)) - 别依赖
len(ch) == cap(ch)判断是否满——这是瞬时快照,竞态下不可靠;要用select+default做非阻塞保护
同步 vs 异步的核心判断依据是“谁决定流程继续”
同步模式下,发送方的下一步执行,取决于接收方是否完成接收;异步模式下,发送方发出即走,后续逻辑不等待对方处理结果。
这个区别直接决定你该用哪种 channel,而不是看“要不要缓冲”这种表面特征。
- 需要等待结果再往下走(比如初始化后才启动 HTTP server)→ 用无缓冲 channel
- 只管发、不关心谁处理/何时处理(比如日志采集、指标上报)→ 用有缓冲 channel + 显式 close
- 既想发得快、又怕丢数据、还要能感知失败 → 用带超时的
select:select { case ch
最容易被忽略的坑:channel 操作没有必然成功保障时,裸写就等于赌调度
只要存在接收方可能提前退出、上下文取消、网络超时等不确定性,ch 或 <code><-ch 就不是原子安全操作。
裸写不加保护,等于把 goroutine 生死交给 Go 调度器——它不会帮你重试、不会自动降级、也不会报错提示,只会静默卡住。
复杂点在于:这种卡死往往只在高负载、低资源、特定调度顺序下复现,本地测试很难暴露。所以凡涉及不确定接收方生命周期的 channel 通信,必须配 select + timeout 或 default 分支,哪怕只是打个日志或 fallback 到本地队列。


















