goroutine 启动后需显式等待和错误处理:用 sync.waitgroup 记录数量并调用 done;循环中传参捕获当前值;阻塞操作须加超时;信号传递优先用 chan struct{}。

goroutine 启动后就失控?用 go 关键字只是开始
启动 goroutine 本身极简单:在函数调用前加 go。但真正的问题从来不是“怎么启”,而是“启了之后怎么知道它跑没跑、跑完没、出没出错”。很多人写完 go doWork() 就以为完事了,结果主函数退出,doWork 根本没执行——因为 main 函数结束,整个程序就终止了。
- 必须显式等待,常见做法是用
sync.WaitGroup记录启动数量并wg.Done()通知完成 - 别在
for循环里直接传循环变量(如go func(i int){...}(i)),否则所有 goroutine 可能共享同一个最终值;应传参捕获当前值 - 如果 goroutine 内部有阻塞操作(如网络请求、channel 读写),没超时控制或错误处理,它可能永远卡住,且无法被外部感知
channel 传数据还是传信号?选错类型会卡死
用 chan int 传数字没问题,但若只是想“通知一件事做完”,用 chan struct{} 更轻量、语义更准。很多初学者误用带缓冲的 channel(如 make(chan int, 1))当信号量,结果因忘记 len(ch) == cap(ch) 导致后续 ch 永久阻塞。
- 纯信号场景:用
chan struct{}+close(ch)或ch ,接收方用 <code> 或 <code>_, ok := - 需要缓冲防丢信号:缓冲大小设为 1 即可,别设大;设大了容易掩盖逻辑缺陷(比如本该同步等待却异步堆积)
- 从 nil channel 读写会永久阻塞;从已关闭的 channel 读会立即返回零值,写则 panic —— 别在不确定状态下调用
close()
defer 在 goroutine 里失效?它只对当前 goroutine 生效
defer 不跨 goroutine。你在主 goroutine 里写 defer cleanup(),它不会等你另起的 go doTask() 结束才执行。反过来,在 go 启动的函数内部写 defer,只对那个 goroutine 的生命周期负责。
- 资源清理必须放在 goroutine 内部做,比如打开文件后立刻
defer f.Close(),而不是指望外面包一层defer - 数据库连接、HTTP client transport 等长生命周期对象,别在 goroutine 里反复新建;应复用,并确保其关闭时机可控(比如用
sync.Once或服务 shutdown 阶段统一关) - 日志记录类操作如果用了带缓冲的 writer,goroutine 退出前记得
flush(),否则日志可能丢失
panic 会杀死 goroutine,但 recover 不会自动传播
goroutine 内 panic,默认只终止自己,不会让主程序崩溃——这是设计特性,不是 bug。但这也意味着:如果你没在 goroutine 内部 recover,panic 就静默消失了,连日志都没有,问题极难定位。
立即学习“go语言免费学习笔记(深入)”;
- 所有长期运行的 goroutine(如
for { select { ... } })建议包裹defer func(){ if r := recover(); r != nil { log.Printf("panic: %v", r) } }() -
recover()只在defer函数中有效,且只捕获同 goroutine 的 panic;跨 goroutine 的 panic 无法被外部recover - 不要用 panic 做业务错误控制(比如参数校验失败),它开销大、不可控;该用
error返回的,就老实用if err != nil
goroutine 的边界感很弱——它既不自动绑定生命周期,也不自带错误传播,更不保证调度时机。写的时候多问一句:“这个 goroutine 死了,我能不能立刻知道?” 答案如果不是“能”,就得补机制。











