go 后必须跟函数调用(如 go f() 或 go func(){}()),不可只写函数名;循环中启动 goroutine 需传参避免变量复用;main 退出会强制终止所有 goroutine,应使用 sync.waitgroup 或 chan 正确同步。

go 后面必须跟函数调用,不能只写函数名
直接写 go f 是语法错误,Go 编译器会报 cannot use f (type func()) as type struct{} in go statement。goroutine 启动的唯一合法形式是 go f() 或 go func(){}() —— 关键在括号,它代表“现在就调用”。
- 函数值本身(如
f)只是地址,不触发执行;加括号才是表达式,才能被调度器纳入队列 - 返回值会被静默丢弃:哪怕
f()返回int或error,你也拿不到——需要结果就得改用chan或传指针 - 常见误写:
for _, v := range items { go process(v) }→ 实际上所有 goroutine 都可能读到同一个v的最终值(循环变量复用),正确写法是go process(v)改为go func(val string) { process(val) }(v)
main 退出就全杀,别靠 time.Sleep 等
程序生命周期由 main 函数决定:一旦 main 返回,运行时立刻终止所有 goroutine,不管它们是否还在打印、写文件或发 HTTP 请求。你看到“没输出”“日志断了”“数据库没写入”,90% 是这个原因。
-
time.Sleep不是同步机制,只是碰运气——时长难估、不可靠、掩盖真实依赖 - 真正该用的是
sync.WaitGroup:启动前wg.Add(1),goroutine 结尾defer wg.Done(),main末尾阻塞调用wg.Wait() - 如果任务有返回值或需控制完成顺序,优先选
chan:比如用results := make(chan int, len(tasks))收集结果,配合for i := 0; i
大量启动 goroutine 容易崩,得控数量
写 for i := 0; i 看似简单,实则危险:内存暴涨、调度延迟飙升、甚至触发 <code>runtime: out of memory。Go 的 GMP 调度器不是魔法,它管理的是“轻量级”,但十万级活跃 G 仍会吃光栈内存和上下文切换资源。
- 真实服务场景应使用 worker pool:固定 N 个长期运行的 goroutine,从
taskChan持续取任务执行 - 任务通道建议带缓冲(如
make(chan func(), 1024)),防提交方因瞬时积压而阻塞 - 关闭 pool 时,先
close(taskChan),再wg.Wait();顺序反了,worker 可能永远卡在range上
长期任务必须支持取消,context.Context 不是可选项
HTTP handler、轮询接口、定时任务这类 goroutine,若不监听取消信号,就会变成“幽灵协程”:进程看似退出,实际还在后台跑,泄漏连接、goroutine、内存。Go 标准库里几乎所有 I/O 接口(http.Client.Do、net.Conn.Read、time.AfterFunc)都接受 context.Context。
- 别用全局 flag 或
var done bool轮询判断——效率低、竞态风险高、无法传递超时语义 - 正确做法:用
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)创建子 ctx,在 goroutine 内用select监听ctx.Done(),收到后清理资源并 return - 注意 context 生命周期:不要把短命的 request ctx 传给长周期后台 goroutine;如需传播 cancel,应在 task 内部调用
childCtx, cancel := context.WithCancel(ctx),并在执行完显式cancel()
goroutine 的创建门槛极低,但它的生命周期管理恰恰是最容易被跳过的部分。很多人卡在“为什么没打印”,其实问题不在 go 怎么写,而在没想清楚:它该活多久?谁来通知它停?结果往哪送?
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











