goroutine 启动后立即执行但不保证完成,需用 sync.WaitGroup 或 channel 显式同步;channel 是安全通信唯一方式,遵循“通过通信共享内存”原则;worker pool 模式结合 select 超时可构建健壮并发模型。

goroutine 启动后立即执行,但不保证执行完成
Go 的 go 关键字启动的是轻量级协程,它不阻塞主线程,也不提供默认的等待机制。如果你写 go doTask() 然后直接退出 main 函数,任务大概率不会执行完——因为程序已结束,所有 goroutine 被强制终止。
常见错误现象:main 函数返回后控制台无任何输出,或只输出部分日志。
- 用
sync.WaitGroup显式等待:在启动前wg.Add(1),函数末尾wg.Done() - 避免用
time.Sleep模拟等待——不可靠、难维护、掩盖同步逻辑缺陷 - 若任务有返回值或错误,不能靠 goroutine 直接传回,需配合 channel 或其他同步机制
channel 是 goroutine 间通信的唯一安全方式(非共享内存)
Go 推崇“不要通过共享内存来通信,而应通过通信来共享内存”。这意味着你不能让多个 goroutine 直接读写同一个全局变量并指望它线程安全;必须用 chan 来传递数据。
使用场景:任务分发、结果收集、限流、信号通知等。
立即学习“go语言免费学习笔记(深入)”;
- 无缓冲 channel(
make(chan int))会阻塞发送和接收,适合同步点控制 - 有缓冲 channel(
make(chan string, 10))可暂存数据,但满时发送仍阻塞 - 关闭 channel 后,接收操作仍可读取剩余数据,之后返回零值+
false,这是判断生产者结束的标准方式 - 对已关闭的 channel 再次关闭会 panic,务必确保只关一次
典型异步任务处理模型:worker pool + channel
这不是玩具示例,而是生产中常见的并发控制模式:固定数量 worker 复用 goroutine,从任务 channel 拿活干,结果写入另一 channel。
性能影响:worker 数量通常设为 CPU 核心数或略高;过多 goroutine 反而因调度开销降低吞吐;过少则无法压满 I/O 或计算资源。
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// 启动 3 个 worker
for w := 0; w < 3; w++ {
go worker(jobs, results)
}
// 发送 5 个任务
for j := 0; j < 5; j++ {
jobs <- j
}
close(jobs)
// 收集全部结果
for a := 0; a < 5; a++ {
<-results
}}
func worker(jobs
select + timeout 防止 channel 永久阻塞
单个 channel 操作可能永远等不到数据(比如生产者崩溃未关闭 channel),导致 goroutine 卡死。用 select 加 default 或 time.After 可主动超时。
容易踩的坑:在循环中频繁创建 time.After 会泄漏 timer;应复用或改用 time.NewTimer 并记得 Reset 或 Stop。
-
select中多个 case 同时就绪时,执行顺序是随机的,不能依赖先后 case 若 ch 为空且未关闭,该分支永不触发;加default则立刻执行非阻塞分支- 需要精确超时时,用
timer := time.NewTimer(2 * time.Second); defer timer.Stop()
实际写异步任务时,最常被忽略的是「谁负责关闭 channel」和「谁负责回收 goroutine」。这两个问题没理清,程序不是漏数据就是泄漏 goroutine,而且问题往往在线上低概率出现,排查成本很高。










