文档【免费下载链接】uber_go_guide_cnUber Go 语言编码规范中文版. The Uber Go Style Guide .项目地址https://gitcode.com/gh_mirrors/ub/uber_go_guide_cn点击查看免费下载导读本文讲解 Uber Go 语言编码规范中文版uber-go/guide中的一条核心指导init()函数中不应启动 goroutine。当某个包确实需要后台 goroutine 时规范要求包必须暴露一个负责管理 goroutine 生命周期的对象并提供一个如Close、Stop、Shutdown能通知 goroutine 停止并等待其退出的方法。读完本文你将掌握避免在包加载阶段无条件泄漏后台任务的正确设计模式以及如何把这条规则与避免使用init()、等待 goroutine 退出、不要 fire-and-forget goroutine等相邻规范组合落地。规则原文init()不产生 goroutinegoroutine-init.md 给出的规则非常简洁明确init()functions should not spawn goroutines.init()函数不应启动 goroutine。它同时指向了相邻规范 Avoid init()二者构成一组完整约束init()本身就应该尽量避免即便不得已使用也不得在其中启动后台 goroutine。如果包需要后台 goroutine则必须满足以下条件包必须暴露一个负责管理 goroutine 生命周期的对象该对象必须提供Close、Stop、Shutdown等方法通知后台 goroutine 停止并等待它退出。这一约定背后是两个原则调用方对后台任务的可控性与可回收性——用户有权决定任务何时启动、何时结束并能确保任务真正退出后再释放资源。反模式在init()中无条件启动后台 goroutine规范用下面的 Bad 示例说明需要避免的写法func init() { go doWork() } func doWork() { for { // ... } }这段代码的问题非常典型只要用户import 了这个包init()就会执行后台 goroutine 便被无条件启动——用户甚至不需要调用任何函数用户对 goroutine完全没有控制权没有句柄、没有信号通道也就没有任何停止它的手段goroutine 内部是一个for {}死循环会一直运行直到进程退出造成资源持续占用。这与 goroutine-forget.mdDont fire-and-forget goroutines的警告一脉相承goroutine 虽轻量但并非免费——至少占用栈内存与调度 CPU大量无生命周期控制地启动会造成性能问题还会阻止无用对象被 GC 回收、长期占用不再使用的资源。在init()中启动的 goroutine 正是fire-and-forget的最极端形态连启动动作本身都不在用户的控制范围内。正确模式暴露管理生命周期的工作对象规范给出的 Good 示例是一个典型的Worker 对象 信号通道chan struct{}设计type Worker struct{ /* ... */ } func NewWorker(...) *Worker { w : Worker{ stop: make(chan struct{}), done: make(chan struct{}), // ... } go w.doWork() } func (w *Worker) doWork() { defer close(w.done) for { // ... case -w.stop: return } } // Shutdown tells the worker to stop // and waits until it has finished. func (w *Worker) Shutdown() { close(w.stop) -w.done }这个设计解决了反模式中的所有问题值得逐点拆解设计要点实现方式解决的问题按需启动goroutine 在NewWorker(...)构造函数中启动而非init()只有用户主动请求创建 Worker 时后台任务才会运行可停止stop通道用于向 goroutine 发出退出信号close(w.stop)触发case -w.stop: return用户拥有停止后台任务的明确手段可等待done通道由 goroutine 在退出时通过defer close(w.done)关闭Shutdown()用-w.done阻塞等待调用方能确认任务已真正结束避免声称停止但仍在后台运行可回收任务退出、通道关闭后Worker 及其引用资源可被 GC 回收消除资源泄漏Shutdown()的实现值得特别注意close(w.stop)只负责发出信号-w.done负责等待确认。这两步缺一不可——只发信号不等确认调用方无法得知任务何时真正退出这正是 goroutine-exit.mdWait for goroutines to exit中每个由系统产生的 goroutine 都必须有一种方式等待其退出的具体落地。方法命名约定规范明确指出这类管理方法可以使用Close、Stop、Shutdown等命名。实际选择取决于语义Stop/Shutdown强调停止后台运行如本示例Close更契合io.Closer风格适用于需要显式释放资源的场景。无论选用哪个名字契约都一致通知 goroutine 停止 阻塞等待其退出。推荐在方法注释中像示例那样明确写出这一契约例如// Shutdown tells the worker to stop and waits until it has finished.。多个 goroutine 时改用sync.WaitGroup规范特别注明如果 Worker 管理者多个 goroutine应使用WaitGroup并指向 等待 goroutines 退出。原因在于done通道模式只适合单个 goroutine的场景——一个通道只能被关闭一次无法精确等待 N 个 goroutine 全部退出。goroutine-exit.md 给出了两种等待模式的取舍多个 goroutine使用sync.WaitGroupvar wg sync.WaitGroup for i : 0; i N; i { wg.Add(1) go func() { defer wg.Done() // ... }() } // To wait for all to finish: wg.Wait()单个 goroutine使用 goroutine 退出时关闭的chan struct{}done : make(chan struct{}) go func() { defer close(done) // ... }() // To wait for the goroutine to finish: -done据此管理多 goroutine 的 Worker 可重构为type Worker struct { stop chan struct{} wg sync.WaitGroup } func NewWorker(...) *Worker { w : Worker{stop: make(chan struct{})} for i : 0; i N; i { w.wg.Add(1) go func() { defer w.wg.Done() for { select { case -w.stop: return default: // 工作逻辑 } } }() } return w } func (w *Worker) Shutdown() { close(w.stop) w.wg.Wait() // 等待所有 goroutine 退出 }为什么是init()就不行与 Avoid init() 规范的呼应要真正理解这条规则需要回到它引用的 Avoid init()避免使用init()。该规范要求当init()不可避免或确实可取时代码应当完全确定性deterministic与程序环境或调用方式无关不依赖其他init()的调用顺序或副作用——init()顺序虽为人知但代码会变化依赖关系会让代码变得脆弱易错不访问或修改全局、环境状态机器信息、环境变量、工作目录、程序参数等避免 I/O文件系统、网络、系统调用。在init()中启动 goroutine 几乎同时违背以上多条它产生了不可预测的并发行为不确定性、引入了对外部状态的隐式依赖且无法被调用方控制。因此规范强调无法满足上述要求的代码应当成为main()或程序生命周期中的其他位置调用的辅助函数而不是在包加载阶段做init magic。特别地供其他程序使用的库更要保证完全确定性。该规范也承认某些场景下init()可能是可取或必要的例如无法用单条赋值语句表示的复杂表达式可插拔钩子如database/sql方言、编码类型注册表等对确定性的预计算优化。但即便在这些场景下启动后台 goroutine 依然不属于init()的合理用途——后台任务的启动与停止必须显式、可控。实战清单把规则组合落地将本条规则与仓库中的相邻规范组合可以得到一组可操作的检查清单不要在init()中写go ...——启动后台任务的唯一正确位置是显式的构造函数或main()流程中。后台任务必须有可预测的停止时机或有可向其发送停止信号的通道见 goroutine-forget.md。必须提供阻塞等待 goroutine 退出的途径单个 goroutine 用chan struct{}多个用sync.WaitGroup见 goroutine-exit.md。管理对象暴露Close/Stop/Shutdown方法并在文档注释中写明停止并等待退出的契约。在可能产生 goroutine 的包中用go.uber.org/goleak测试 goroutine 泄漏见 goroutine-forget.md确保测试结束后没有残留后台任务。让代码通过go vet与 lint 检查仓库推荐使用errcheck、goimports、golint、govet、staticcheck等基础 linter并以golangci-lint作为统一运行器见 lint.md。小结不要在init()中使用 goroutine是 Uber Go 编码规范中goroutine 生命周期管理三件套不要 fire-and-forget goroutine → 等待 goroutine 退出 → 不要在init()中使用 goroutine的最后一环。它的核心主张是后台任务的生命周期必须显式、可控、可等待。与其在包加载时偷偷启动一个用户无法停止的 goroutine不如提供一个拥有Stop/Shutdown/Close方法的 Worker 对象把何时启动、何时停止、是否已退出的决定权交还给调用方——这正是生产级 Go 代码应有的姿态。赞分享文档【免费下载链接】uber_go_guide_cnUber Go 语言编码规范中文版. The Uber Go Style Guide .项目地址https://gitcode.com/gh_mirrors/ub/uber_go_guide_cn点击查看免费下载相关推荐Uber Go Style Guide不要在 init() 中启动 goroutine——用生命周期对象管理后台任务Uber Go Style Guide不要在 init 中启动 goroutine——用生命周期对象管理后台任务 init 是 Go 包加载时自动执行的初始化文档教程代码质量LintUber Go Style Guide 实践拒绝“发射后不管”的 goroutine把生命周期握在自己手里Uber Go Style Guide 实践拒绝“发射后不管”的 goroutine把生命周期握在自己手里 Go 的 goroutine 非常轻量但这并不文档教程代码质量LintNJsonSchema完全指南.NET开发者必备的JSON Schema解析与验证工具NJsonSchema完全指南.NET开发者必备的JSON Schema解析与验证工具 NJsonSchema是一款专为.NET开发者打造的强大JSON Sc开发工具上一篇智能音箱AI改造终极指南3步让小爱音箱变身ChatGPT语音助手下一篇Erlang/OTP 时间校正Time Correction与时间回拨模式完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考