在 Go 语言中,单例模式的实现与线程安全(Go 中称为 协程安全并发安全)可以通过多种方式实现。以下是几种常见的方法:


1. 使用 sync.Once(推荐)

sync.Once 是 Go 标准库中专门设计用来确保某个操作只执行一次的机制,非常适合实现单例模式。

package singleton

import "sync"

type Singleton struct {
    // 单例对象的字段
}

var (
    instance *Singleton
    once     sync.Once
)

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
        // 初始化单例对象的字段
    })
    return instance
}

原理:

  • once.Do 保证内部的初始化函数只会执行一次,无论有多少协程同时调用 GetInstance
  • 线程安全,无需手动管理锁。

优点:

  • 简洁高效,无锁竞争。
  • 延迟初始化(懒加载),只有在首次调用时创建实例。

2. 使用互斥锁(sync.Mutex

通过互斥锁确保初始化过程线程安全:

package singleton

import "sync"

type Singleton struct {
    // 单例对象的字段
}

var (
    instance *Singleton
    mu       sync.Mutex
)

func GetInstance() *Singleton {
    if instance == nil { // 第一次检查,避免不必要的锁竞争
        mu.Lock()
        defer mu.Unlock()
        if instance == nil { // 第二次检查,确保实例未被其他协程创建
            instance = &Singleton{}
            // 初始化单例对象的字段
        }
    }
    return instance
}

原理:

  • 双重检查锁定(Double-Checked Locking)减少锁的竞争。
  • 第一次检查避免每次调用都加锁,第二次检查确保实例唯一。

缺点:

  • 代码稍复杂,需要手动管理锁。

3. 包级变量直接初始化

如果单例对象无需延迟初始化,可以直接在包初始化时创建:

package singleton

type Singleton struct {
    // 单例对象的字段
}

var instance = &Singleton{
    // 初始化字段
}

func GetInstance() *Singleton {
    return instance
}

原理:

  • Go 的包初始化是线程安全的,instance 会在程序启动时自动创建。
  • 适合简单场景,但无法延迟初始化。

缺点:

  • 如果初始化耗时,可能影响程序启动速度。

4. 利用 init 函数

init 函数中初始化单例:

package singleton

type Singleton struct {
    // 单例对象的字段
}

var instance *Singleton

func init() {
    instance = &Singleton{}
    // 初始化字段
}

func GetInstance() *Singleton {
    return instance
}

原理:

  • init 函数在包被导入时自动执行,且只会执行一次。
  • 线程安全,但无法延迟初始化。

5. 避免单例模式(依赖注入)

在 Go 中,单例模式有时被认为是反模式。更好的做法是通过 依赖注入全局配置 来管理共享资源:

// 将单例作为参数传递,而非全局变量
type Singleton struct {
    // 字段
}

func NewSingleton() *Singleton {
    return &Singleton{}
}

// 使用依赖注入
func Process(s *Singleton) {
    // 使用单例对象
}

优点:

  • 避免全局状态,提升代码可测试性和可维护性。

总结

  • 推荐方式:使用 sync.Once,简洁高效且线程安全。
  • 延迟初始化:优先选择 sync.Once 或双重检查锁定。
  • 简单场景:直接使用包级变量或 init 函数。
  • 最佳实践:尽量避免全局单例,优先通过依赖注入管理共享资源。

示例(完整版)

package singleton

import (
    "sync"
)

type singleton struct {
    // 单例对象的字段
    Value int
}

var (
    instance *singleton
    once     sync.Once
)

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{
            Value: 42, // 初始化字段
        }
    })
    return instance
}