Go并发基于goroutine+channel协作调度,非多线程模拟;goroutine由GMP模型管理,轻量级且复用OS线程;需用WaitGroup/errgroup等待、worker pool限流,避免goroutine泄漏;无缓冲channel是同步开关。

Go语言并发编程不是“多线程模拟”,而是用 goroutine + channel 构建的协作式任务流——它不靠锁抢资源,靠通信来调度。
goroutine 不是线程,别用线程思维启动它
你写 go doWork(),Go 运行时不会立刻分配 OS 线程,而是把 doWork 排队进一个共享的 GMP 调度队列(G=goroutine, M=OS thread, P=processor)。成千上万个 goroutine 可能只跑在 4 个 OS 线程上。
- 错误做法:为每个 HTTP 请求起一个 goroutine 却不控制数量 → 内存暴涨、调度延迟飙升
- 正确做法:用
sync.WaitGroup或errgroup.Group显式等待;高负载场景必须加 worker pool 限流 - 陷阱:主函数退出时,未等待的 goroutine 会被直接杀死 ——
time.Sleep是临时方案,不是解法
channel 是同步开关,不是消息队列
ch := make(chan int) 创建的是无缓冲 channel,ch 和 必须**同时就绪**才通行。它本质是两个 goroutine 的“握手协议”,不是用来攒数据的缓存。
- 常见错误:往无缓冲 channel 发送后不配接收方 → 程序死锁(panic: all goroutines are asleep)
- 缓冲 channel(
make(chan int, 10))只是放宽了同步要求,不代表能“异步发送”——发满后仍会阻塞 - 真实用途:任务分发(如 worker pool)、信号通知(
done := make(chan struct{}))、超时控制(配合select+time.After)
select 不是 switch,它是 channel 的“多路监听器”
select 不按代码顺序执行,而是随机挑选一个**就绪的 case**;没有就绪项时,default 立刻执行;没有 default 就永久阻塞。
立即学习“go语言免费学习笔记(深入)”;
- 典型误用:在循环里反复
select但没加default→ 某个 channel 长期无数据,整个 goroutine 卡死 - 实用组合:
select+context.WithTimeout实现带超时的 RPC 调用 - 注意:
select中的是“尝试读”,不是“读完再走”,一次只收一个值
真正难的不是写对第一个 goroutine,而是设计好 channel 的生命周期:谁关、何时关、关了之后另一端怎么感知。很多死锁和 panic 都源于 close 调用时机错乱或漏关 —— 这比语法细节更值得花时间推演。











