
本文介绍一种不依赖全局变量、符合 Go 语言惯用法的方式,将 main() 中初始化的运行时配置(如数据库连接)安全、可测试地共享至其他包或文件。核心方案是基于惰性初始化的线程安全单例模式。
本文介绍一种不依赖全局变量、符合 go 语言惯用法的方式,将 main() 中初始化的运行时配置(如数据库连接)安全、可测试地共享至其他包或文件。核心方案是基于惰性初始化的线程安全单例模式。
在 Go 应用中,main() 函数通常是唯一能按需初始化复杂依赖(如已建立连接的数据库实例、配置解析器、日志句柄等)的地方。但这些资源又常被多个业务逻辑函数或工具函数所需——直接使用全局变量虽简单,却会破坏可测试性、导致包间隐式耦合,并使并发安全难以保障。
推荐做法是:将 RuntimeConfig 封装为线程安全的单例,并通过显式初始化(而非自动惰性创建)来确保控制权仍在 main() 手中。这既避免了“未初始化即访问”的 panic,又保留了依赖注入的清晰性。
以下是实现示例:
// config/runtime.go
package config
import (
"sync"
)
type RuntimeConfig struct {
DB *DatabaseInstance
AppName string
// 其他字段...
}
var (
instance *RuntimeConfig
once sync.Once
)
// MustInit 必须由 main() 显式调用,仅允许一次。
// 若重复调用,panic 以明确失败点。
func MustInit(cfg *RuntimeConfig) {
once.Do(func() {
if cfg == nil {
panic("config.MustInit: nil RuntimeConfig provided")
}
instance = cfg
})
if instance == nil {
panic("config.MustInit: failed to initialize singleton")
}
}
// Get 返回已初始化的 RuntimeConfig 实例。
// 若尚未初始化,直接 panic —— 这是设计使然:强制开发者关注初始化顺序。
func Get() *RuntimeConfig {
if instance == nil {
panic("config.Get(): RuntimeConfig not initialized; call config.MustInit() in main()")
}
return instance
}// main.go
func main() {
db := ConnectToDB(/* ... */)
cfg := &config.RuntimeConfig{
DB: db,
AppName: "My app",
// ...
}
config.MustInit(cfg) // ✅ 关键:显式、可控、仅一次
// 启动 HTTP server、worker 等...
}// elsewhere.go
import "yourapp/config"
func SomeUtilityFuncThatNeedsRuntime(i int) int {
runtime := config.Get() // ✅ 安全获取,类型明确,无 nil 风险
runtime.DB.Save(runtime.AppName, i)
return i + 1
}✅ 优势总结:
-
无隐式全局状态:
MustInit强制初始化动作显式化,消除“谁初始化、何时初始化”的歧义; -
线程安全:
sync.Once保证多 goroutine 并发调用MustInit仍只执行一次; -
可测试友好:单元测试中可在
TestMain或每个测试前调用MustInit注入 mock 配置; - 零反射、零依赖注入框架:纯 Go 标准库实现,轻量且易理解。
⚠️ 注意事项:
- 切勿在
init()函数中调用MustInit—— 初始化顺序不可控; -
Get()的 panic 是有意为之的设计决策,它比静默返回nil更利于早期发现配置遗漏; - 如需支持热重载或多环境配置,应升级为更高级的依赖容器(如 Uber FX),但对绝大多数应用,此单例模式已足够稳健。

















