无缓冲通道发送后无人接收必然导致死锁:ch := make(chan int) 创建的无缓冲通道,ch

无缓冲通道发送后没人接收就卡死
这是最典型的阻塞起点:ch := make(chan int) 创建的通道,ch 会立刻挂起当前协程,直到有另一个协程执行 <code>。主协程自己发自己收,等于原地等待自己醒来——Go 运行时检测到所有协程都睡着了,直接报 <code>fatal error: all goroutines are asleep - deadlock!。
- 必须确保发送和接收在**不同协程**中进行,哪怕只是启动一个匿名协程:
go func() { ch - 别在主线程里既发又收(除非用带缓冲通道或
select配default) - 如果只打算单次通信,优先考虑带缓冲通道(如
make(chan int, 1)),它能“暂存”一次发送,避免强同步依赖
for range 读通道却忘了 close
写成 for v := range ch { ... } 是常见写法,但它隐含一个前提:发送方最终会调用 close(ch)。否则接收方永远等下一个值,协程永久阻塞——尤其当发送方是独立协程且已退出,但没关通道时,问题更隐蔽。
- 关闭通道的责任在**最后一个发送方**,不是接收方;接收方调
close是错误用法 - 多个发送协程时,用
sync.WaitGroup计数,全部发完再close,不能靠某个协程猜“大概发完了”就关 - 如果不确定是否要关(比如通道被复用),改用
select+ 超时或退出信号控制循环,而不是无条件range
带缓冲通道满/空时仍强行读写
缓冲通道不是万能解药。make(chan int, 2) 只能存 2 个值:第 3 次 ch 会阻塞,直到有人 <code> 取走至少一个;同理,空缓冲通道上执行 <code> 也会阻塞。
- 缓冲容量要匹配实际并发节奏——比如每秒 10 条日志,缓冲设 100 是安全的;但若突发 1000 条,仍可能堵住
- 不要假设“有缓冲就不会阻塞”,它只是把阻塞点从“立刻”延后到“缓冲满/空时”
- 对关键路径,建议搭配
select做非阻塞尝试:select { case ch
select 里没写 default 导致全 case 都卡住
select 本意是多路复用,但如果所有 case 对应的通道都不可操作(比如全满或全空),且没加 default,那当前协程就停在这儿不动了——不是死锁,但效果类似:协程挂起,没人唤醒。
- 只要逻辑允许跳过,就加
default:分支做兜底(哪怕只写time.Sleep(1 * time.Millisecond)) - 需要精确控制超时?用
time.After或context.WithTimeout构造超时case,比轮询更可靠 - 注意
select是随机选一个就绪的case执行,不是按顺序;如果多个case同时就绪,选哪个不保证
真正容易被忽略的是:阻塞本身不是 bug,而是 Go 并发模型的设计事实。关键不在“怎么让它不阻塞”,而在“谁该等、等多久、等不到怎么办”。通道的阻塞语义是同步契约的一部分,滥用非阻塞(比如狂打 default)反而会让逻辑碎片化、难维护。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











