根本原因是循环变量 i 被所有 goroutine 共享,待 goroutine 实际执行时 i 已为终值;正确做法是在循环内用局部变量或传参捕获当前 i 值。

Go 里并发不是靠“加线程”硬堆出来的,而是靠 goroutine + channel 这对组合自然生长出来的。用错通道类型、漏关通道、在循环里误捕变量——这些不是小疏忽,是直接触发死锁或数据错乱的开关。
goroutine 启动后主函数退出,为什么什么都没打印?
这是新手最常遇到的“黑屏”问题:写了 go sayHello(),但程序一闪就结束,没输出。
- 根本原因:主
goroutine执行完main()函数就退出,整个进程终止,其他goroutine不会等它 -
time.Sleep是临时解法,但不可靠——你无法预知所有子协程执行耗时 - 正确做法是用
sync.WaitGroup显式等待:调用wg.Add(1)计数,子协程末尾调用wg.Done(),主协程用wg.Wait()阻塞直到全部完成 - 别把
WaitGroup当全局变量传;应作为参数传入协程函数,避免闭包捕获导致计数错乱
无缓冲通道 vs 有缓冲通道:什么时候该用哪个?
通道不是“能用就行”,类型选错会导致逻辑卡死或行为不可控。
- 无缓冲通道(
make(chan int)):发送即阻塞,必须有接收方同时就绪——适合做同步信号,比如“任务开始”“步骤完成”“限流闸门” - 有缓冲通道(
make(chan string, 10)):缓冲区未满即可发,未空即可收——适合解耦生产/消费节奏,比如日志收集、批量任务分发 - 缓冲大小不是越大越好:设为 0 就退化成无缓冲;设太大可能掩盖背压问题,让内存持续上涨
- 注意:向已关闭的无缓冲通道发送会 panic;向已关闭的有缓冲通道发送也会 panic(即使缓冲区还有空位)
for 循环里启动 goroutine,为什么全打印同一个数字?
代码看着很顺:for i := 0; i ,结果却输出三个 <code>3。
- 问题出在闭包捕获的是变量
i的地址,不是值;循环结束时i == 3,所有协程读到的都是这个终值 - 修复方式只有一种可靠写法:把
i当作参数传进去,go func(val int) { fmt.Println(val) }(i) - 切勿用
var j = i再闭包——如果j在循环外声明,问题依旧;必须确保每次迭代都生成独立绑定 - Go 1.22+ 对部分场景做了静态分析警告,但不能依赖它来发现所有闭包陷阱
channel 关闭后还能不能读?能不能写?
关闭通道不是“删除通道”,而是一个明确的状态变更,直接影响所有后续操作。
- 向已关闭的通道写(
ch )会 panic —— 运行时报错,无法 recover - 从已关闭的通道读(
)不会 panic:如果有剩余数据,正常返回;如果缓冲区空了,返回零值 + <code>false(如v, ok := 中 <code>ok==false) - 只应由“发送方”关闭通道;多个协程同时发送时,必须协调好谁来关,否则容易重复关闭 panic
-
range遍历通道会自动在关闭后退出,但前提是通道确实被关闭了——忘了关,range就永远卡住
真正难的不是写对第一版并发代码,而是当并发数从 10 上升到 1000、从本地测试跑到高负载服务器时,那些不显眼的通道关闭时机、缓冲区大小选择、WaitGroup 计数位置,会突然变成压垮系统的最后一根稻草。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











