我想了解一下进程、协程、通道、
WaitGroup这四者的相互协作
一、 协程的“寄生”本性
- 进程是载体:Go 程序运行在进程中。
main函数是主协程。 - 生命周期绑定:主协程一旦踏过最后一个大括号
}退出,整个进程直接销毁。所有的子协程(无论是在干活还是在睡觉)都会被强制杀掉。 - 独立性:协程是逻辑独立的,但不具备系统级进程(如 PHP/Java 进程)那种“孤儿存活”的能力。
二、 如何“撑住”一个协程?(不让它死)
协程跑完代码就销毁。要让它长久运行,只有三种办法:
- 循环撑:
for { ... }或for range ch。这是最常用的,让代码逻辑在循环里打转。 - 阻塞撑:执行到
<-ch(读通道)或ch <- x(写通道)时,如果条件不满足,协程会进入休眠态挂起,不占用 CPU,直到被唤醒。 - 定时撑:
for range ticker.C,本质上也是阻塞在定时器的通道上。
三、 通道(Channel)的唤醒机制
通道不仅是数据传输的管道,更是协程间的闹钟:
- 发送者(Sender):往通道丢数据。如果通道满了(缓冲区满),发送者睡觉。
- 接收者(Receiver):从通道取数据。如果通道空了,接收者睡觉(挂起/阻塞)。
- 激活:一旦对方操作了通道,Go 运行时会立刻唤醒休眠的协程。这叫“由通信来实现内存共享”。
四、for range ch:最优雅的监听方式
这种写法比普通的for循环高级在两点:
- 自动阻塞:没数据时它就在
range那行原地睡觉,不消耗资源。 - 自动退出:它不看通道是否有值,只看通道是否关闭。
- 只要不
close,它就一直等(哪怕等 10 分钟、1 小时)。 - 一旦
close(ch)且通道里没存货了,循环自动跳出,协程优雅销毁。
- 只要不
五、 协作利器:sync.WaitGroup
它是主协程与子协程之间的“查票员”:
Add(n):主程说:“有 n 个乘客上车了,别熄火。”Done():子协程说:“我这站下车了。”(计数器 -1)Wait():主程在大巴车门守着:“计数器不到 0,我就不准关门熄火。”
💡 结语
在 Go 的世界里:
进程是大巴,协程是乘客,通道是对讲机,WaitGroup 是下车登记表。
理解了这四者的关系,你就掌握了 Go 并发编程的底层密码。