goroutine 是 Go 运行时调度的用户态协程,非操作系统线程;启动开销小、数量可达数十万,但需配合 WaitGroup 或 channel 等同步机制确保正确结束。

goroutine 是什么,不是什么
goroutine 不是操作系统线程,也不是“轻量级线程”的通俗化说法——它是 Go 运行时调度的、用户态的执行单元。启动开销极小(初始栈仅 2KB),数量可达数十万,但不意味着可以无节制创建。它的本质是:由 go 关键字触发、由 Go 调度器(GMP 模型)管理的协程。
常见误判:go func(){}() 看似简单,但若函数内无阻塞、无 I/O、无 channel 操作,很可能在主 goroutine 退出前就已执行完毕或被抢占,导致“看不见效果”。
怎么安全启动一个 goroutine 并等待它结束
直接用 go 启动后不处理同步,大概率会丢失结果或 panic。必须配合显式同步机制:
-
sync.WaitGroup:适合已知任务数量、无需返回值的场景。调用wg.Add(1)必须在go前,wg.Done()放在 goroutine 最后(建议 defer) -
channel:适合需要传回结果或控制生命周期的场景。记得关闭 channel 或用带缓冲的 channel 避免阻塞;接收端应使用for range或带select的非阻塞接收 - 避免只用
time.Sleep等待——它不可靠、难维护、掩盖真实依赖关系
示例(WaitGroup):
立即学习“go语言免费学习笔记(深入)”;
AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。它不是新的编程语言,而是一种使用现有标准的新方法,最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。《php中级教程之ajax技术》带你快速
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Println("task", id)
}(i)
}
wg.Wait() // 主 goroutine 阻塞至此
goroutine 泄漏的典型表现和自查点
goroutine 泄漏不会立刻报错,但表现为:程序内存缓慢增长、runtime.NumGoroutine() 持续上升、pprof 查看 goroutine 数量异常高。常见原因:
- 向无人接收的无缓冲 channel 发送数据(永久阻塞)
- 从无人发送的 channel 接收(同样永久阻塞)
- goroutine 内部死循环且无退出条件(尤其搭配 time.Ticker 未 stop)
- HTTP handler 中启 goroutine 但没处理请求上下文取消(
ctx.Done()未监听)
调试建议:运行时加 GODEBUG=gctrace=1 观察 GC 频次;用 curl http://localhost:6060/debug/pprof/goroutine?debug=2 查看堆栈;关键 channel 操作前后打日志或加超时(select { case )
什么时候不该用 goroutine
不是所有并发都该用 goroutine。以下情况优先考虑串行或其它方案:
- 单次计算密集型任务(如大数组排序、加密运算)——CPU 密集型会挤占 P,反而降低吞吐;应结合
runtime.GOMAXPROCS控制并行度,或改用更优算法 - 顺序强依赖逻辑(A 必须等 B 结果才能开始)——强行拆成 goroutine 只会增加 channel 通信开销和复杂度
- 短生命周期、低频调用的小函数(如日志打点、配置读取)——启动 goroutine 的调度成本可能超过函数本身执行时间
- 已有同步原语可解的问题(如单次定时器、一次性的 sync.Once)——多一层 goroutine 就多一层出错面
真正适合 goroutine 的场景很明确:I/O 等待(网络、磁盘、数据库)、多个独立子任务并行、事件驱动响应(如 WebSocket 连接管理)。核心判断标准是:有没有可让出 CPU 的等待点。









