跳到主要内容

单例模式

问题

Go 中如何实现单例模式?为什么推荐用 sync.Once

答案

sync.Once 实现(推荐)

type Database struct {
conn *sql.DB
}

var (
dbInstance *Database
dbOnce sync.Once
)

func GetDB() *Database {
dbOnce.Do(func() {
conn, err := sql.Open("mysql", "dsn...")
if err != nil {
log.Fatal(err)
}
dbInstance = &Database{conn: conn}
})
return dbInstance
}
为什么推荐 sync.Once
  • 线程安全:内部用 atomic + Mutex,保证只执行一次
  • 零配置:不需要双重检查锁
  • 性能好:初始化后走 atomic.Load 快速路径,无锁

其他实现方式对比

包级变量 init(饿汉式)

var db = mustInitDB() // 程序启动时就初始化

func mustInitDB() *Database {
conn, err := sql.Open("mysql", "dsn...")
if err != nil {
log.Fatal(err)
}
return &Database{conn: conn}
}
方式懒加载线程安全推荐
sync.Once⭐⭐⭐
init() / 包变量❌(启动时)⭐⭐
加锁检查

常见面试问题

Q1: sync.Once 的 Do 内部 panic 了会怎样?

答案sync.Once 标记为"已完成",后续调用 Do 不会再执行。即使第一次 panic 了,单例也不会被重新初始化。如果需要重试初始化,不能用 sync.Once,需要自己加锁重试。

Q2: Go 中单例模式在哪些场景使用?

答案

  • 数据库连接池sql.DB 本身就是连接池,只需一个实例
  • 日志器:全局 Logger
  • 配置对象:load 一次全局共享
  • HTTP Client:复用连接的 http.Client

相关链接