用channel接收goroutine返回值是Go唯一推荐方式,因Go无await/join机制;需声明匹配类型的channel,goroutine内发送结果,主goroutine通过<-ch阻塞接收。

用 channel 接收 goroutine 的返回值最直接
Go 没有类似其他语言的 await 或 join() 机制,goroutine 启动后就异步运行,本身不返回值。想拿到结果,必须靠通信——channel 是标准且唯一推荐的方式。
常见错误是试图用局部变量“捕获”结果(比如在 goroutine 里赋值给外部 result 变量),这会引发竞态,而且无法同步等待完成。
- 声明一个带类型的
channel,类型要和你要返回的结果一致,比如chan int、chan string - 启动 goroutine 时把该 channel 作为参数传入,在 goroutine 内部算完后往里
send - 主 goroutine 调用
<-ch阻塞等待,直到结果被写入
ch := make(chan int)
go func(c chan<- int) {
result := heavyComputation()
c <- result // 发送结果
}(ch)
val := <-ch // 接收,阻塞直到有值
多个 goroutine 结果怎么收集:用 for range 配合 close()
如果启了 N 个 goroutine 并行干活,每个都往同一个 channel 写结果,主 goroutine 就不能只读一次——得循环读,但又不能无限等。关键点在于:谁关 channel?什么时候关?
错误做法是让某个 goroutine 自己关 channel(容易 panic:send on closed channel),或者主 goroutine 猜数量去读(不可靠)。
立即学习“go语言免费学习笔记(深入)”;
- 用
sync.WaitGroup管理 goroutine 生命周期,确保全部完成后再close(ch) - 主 goroutine 用
for v := range ch安全读取,range 自动在 channel 关闭后退出 - 注意:
WaitGroup的Add()必须在 goroutine 启动前调用,否则可能漏计数
不想阻塞主线程?用带缓冲的 channel + select 超时控制
如果 goroutine 可能卡住或耗时过长,直接 <-ch 会永久挂起。这时要用 select 配合 time.After 做超时保护。
- 缓冲 channel 不是必须的,但配合超时更稳妥:避免 sender 因没人接收而阻塞
-
select中的case <-ch:和case <-time.After(3 * time.Second):必须并列,不能嵌套 - 别忘了在 timeout 分支里显式处理“结果可能还在路上”的情况(比如加日志、发告警)
ch := make(chan string, 1)
go func() { ch <- doWork() }()
select {
case result := <-ch:
fmt.Println("got:", result)
case <-time.After(2 * time.Second):
fmt.Println("timeout")
}
用 sync.Once 或 sync.Map 替代 channel?别这么干
有人想绕过 channel,用全局变量 + sync.Once 记录结果,或用 sync.Map 存 key-value 对应关系。这在单次调用场景下看似可行,但立刻暴露问题:
- 无法表达“还没开始”“正在执行”“已失败”等状态,只能存最终值,丢失过程信息
- 多个 goroutine 往同一变量写,逻辑极易耦合,调试困难
- 违背 Go 的“通过通信共享内存”原则,后续扩展成并发请求池时几乎必然重构
channel 不只是管道,它天然承载同步语义和生命周期控制——这点最容易被忽略,也是为什么所有标准库和主流项目都坚持用它。


















