函数返回 channel 时必须启动 goroutine 在函数体内发送数据,否则调用方 range 会卡死或 panic,因函数返回后栈帧销毁导致发送未完成。

函数返回 channel 时必须启动 goroutine
直接在函数体里往 chan 发送数据再返回,调用方一 range 就会卡死或 panic。因为函数栈帧在返回后立即销毁,而发送操作尚未完成,ch 会阻塞在已失效的栈上。
正确做法是:函数内部用 go 启动匿名 goroutine,在其中完成所有发送,并在最后 close();函数本身只负责创建 channel 并返回它。
- 必须用
defer close(out),且放在 goroutine 内部,确保所有数据发完才关 - 输入 channel 类型应声明为
(只读),输出为 <code> 或 <code>chan,避免误写 - 若返回无缓冲 channel,调用方需确保有接收者,否则第一个
ch 就永久阻塞
示例:
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}多协程结果聚合要配 error channel 和 WaitGroup
并发调用多个任务并汇总结果时,不能只靠一个 chan Result,否则错误会被吞掉或丢失上下文。常见错误是:某个 goroutine panic 或 send 失败,主协程却还在等剩余数据,最终超时或死锁。
立即学习“go语言免费学习笔记(深入)”;
必须同时管理三件事:任务完成信号、结果数据、错误信息。最稳妥的组合是 chan Result + chan error + sync.WaitGroup。
Web仪表盘,用于审批OpenClaw设备与通道配对、管理连接,并可从浏览器访问实时终端。
-
Result结构体应显式携带Data和Err字段,不依赖 nil 判断 - 每个子 goroutine 都要 defer 调用
wg.Done(),并在出错时向errCh发送而非 panic - 主协程应在
wg.Wait()后关闭resultCh和errCh,再range读取,避免漏收
使用无缓冲 channel 做同步时别忘了配对
无缓冲 channel 的核心价值不是传数据,而是强制两个 goroutine “碰头”——发送和接收必须同时就绪。但很多人只开 channel、只发不收,或者只收不发,导致程序 hang 在 ch 或 <code> 上,且无明确错误提示。
典型场景是初始化等待:子 goroutine 启动后需通知主协程“我准备好了”,这时用 done := make(chan struct{}) 最轻量。
- 永远用
struct{}类型做同步 signal,零内存占用 - 发送端必须确保只 send 一次,且在逻辑就绪后立刻 send
- 接收端要用
而非 <code>select { case ,除非你明确需要超时 - 不要在同一个 goroutine 里既 send 又 recv,这等于自己等自己
带缓冲 channel 不等于“不会阻塞”
很多人以为 make(chan int, 10) 就能随便发 10 次不卡,忽略了缓冲区是环形队列,且收发速度不匹配时仍会阻塞。线上曾有服务因生产者远快于消费者,缓冲区迅速填满,后续所有 ch 全部阻塞,goroutine 积压到数万,OOM 崩溃。
缓冲区大小不是拍脑袋定的,它本质是背压策略的体现。
- 缓冲区设为 0:强同步,适合严格顺序控制
- 缓冲区设为 N:允许最多 N 个未处理任务积压,适合 IO 密集型、延迟可接受的场景
- 缓冲区过大(如 1000+):掩盖设计缺陷,容易掩盖消费者瓶颈,应配合监控(如
len(ch))告警 - 永远检查
cap(ch)和len(ch),尤其在关键路径上打点
真正难的不是写对语法,而是判断“这个 channel 该不该缓冲、缓多少、谁来清空”。这些决策一旦错了,问题往往在线上静默积累数小时才爆发。

















