goroutine需配recover和独立日志;请求数据须提前读取;用带缓冲channel限流并发。

goroutine 直接起就行,但别漏了 recover 和日志
HTTP handler 里直接 go func() { ... }() 是最简方案,但它会脱离请求生命周期——panic 不会被捕获、错误没地方打日志、上下文取消信号也收不到。
- 必须在 goroutine 内部用
defer recover()拦住 panic,否则整个进程可能挂掉 - 别用 handler 里的
log实例,要传入独立 logger 或用结构化日志库(如zerolog)带 trace ID - 如果任务依赖请求数据(比如
req.Body),得提前读完并复制,不能在 goroutine 里直接读,因为req.Body可能已被关闭
需要控制并发数?用带缓冲的 channel 做简单限流
用户猛点提交按钮,100 个耗时任务一起 go 起来,内存和 goroutine 调度压力会陡增。不用上完整 worker pool,一个缓冲 channel 就能卡住并发上限。
- 声明
var taskCh = make(chan func(), 10),容量就是最大并发数 - 启动固定数量的长期 goroutine:
for range taskCh { ... } - handler 里只做
taskCh ,写入即返回,超限时会阻塞(可加 select + timeout 避免卡死) - 注意 channel 关闭后不能再写,要配合
sync.Once或原子变量管理 shutdown 状态
要等结果或传状态?别用全局 map,改用 sync.Map + UUID key
前端轮询“任务进度”,后端就得存中间状态。用普通 map 并发读写会 panic;用 sync.Mutex 包裹又容易锁粒度太粗。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 直接上
sync.Map,key 用uuid.NewString(),value 存结构体(含状态、结果、错误、时间戳) - 避免把大对象(比如原始文件内容)塞进 map,只存指针或摘要,否则 GC 压力大
- 记得设过期清理:启动一个
time.Ticker定期扫sync.Map,删掉超过 5 分钟的旧记录 - 前端传来的
task_id必须校验格式(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$),防恶意遍历
context.WithTimeout 传不进 goroutine?得手动接续
handler 里用了 ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second),但这对 goroutine 无效——子 goroutine 默认继承的是原始 r.Context(),不是你包好的那个。
- 必须显式把新 context 传进去:
go doWork(ctx, data),而不是go doWork(r.Context(), data) - 在
doWork开头就select { case ,别等到底层 HTTP client 或 DB 查询才检查 - DB 查询要用
db.QueryContext(ctx, ...),HTTP 调用要用http.NewRequestWithContext(ctx, ...),否则 timeout 白设 - 别在 goroutine 里调
cancel(),那是 handler 的事;goroutine 只负责监听ctx.Done()并退出
真正麻烦的从来不是“怎么起 goroutine”,而是错误路径是否全覆盖、资源是否及时释放、状态是否可追溯。比如一个没 recover 的 panic,可能让后续所有异步任务静默失败;一个没关的 sql.Rows,会在几十秒后拖垮数据库连接池。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










