Files
oci-portal/.trellis/spec/backend/concurrency.md
T
2026-07-09 19:18:04 +08:00

30 lines
1.6 KiB
Markdown

# 并发规范
> Go 后端并发约定。
- 每个 goroutine 必须有明确退出路径:外部用 `context.Context` 或 stop channel 通知,内部 `select` 响应;禁止 fire-and-forget,不允许 goroutine 泄漏。
- 调用方能等待 goroutine 退出:`sync.WaitGroup``done` channel。
- `init()` 中不启动 goroutine、不做 IO。
- channel 容量只用 0 或 1,更大缓冲须注释说明理由。
- 涉及阻塞或远程调用的函数第一个参数是 `ctx context.Context`,不把 ctx 存进 struct。
## 模式:服务级后台 goroutine 的落地形态(2026-07 起为既定写法)
面板内「异步旁路」(通知发送 `Notifier.SendAsync`、日志异步落库 `SystemLogService.Record`、定期清理 `StartCleanup`)统一三件套:
```go
s.wg.Add(1)
go func() {
defer s.wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // 自带超时,不复用请求 ctx
defer cancel()
if err := s.doWork(ctx); err != nil {
log.Printf("xxx: %v", err) // 旁路失败只记日志,绝不影响主流程
}
}()
```
- 服务暴露 `Wait()`(内部 `wg.Wait()`);常驻循环(如清理 ticker)额外接收 ctx,`select` 响应退出。
- cmd/server 装配的 defer 顺序必须是「先停生产者、再等消费者」(LIFO):`defer notifier.Wait()` / `defer systemLogs.Wait()` 写在 `defer tasks.Stop()` / `defer stopCleanup()` **之前**
- 陷阱:`cron.Stop()` 返回的 context 要等(`<-s.cron.Stop().Done()`),否则仍在执行的任务尚未 `wg.Add``Wait()` 会提前返回,goroutine 泄漏且违反 WaitGroup 的 Add/Wait happens-before 约束。