如何在C++中实现线程安全的单例模式?

在多线程环境下,单例模式的实现必须保证只有一个实例被创建,并且在任何线程里都能安全访问。下面我们从传统实现、C++11 的std::call_once以及使用局部静态变量三种方式,逐步深入探讨。

1. 传统双重检查锁(Double‑Checked Locking)

class Singleton {
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {                // 第一次检查
            std::lock_guard<std::mutex> lock(mtx); // 互斥锁
            if (instance == nullptr) {            // 第二次检查
                instance = new Singleton();
            }
        }
        return instance;
    }

private:
    Singleton() = default;
    static Singleton* instance;
    static std::mutex mtx;
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

关键点

  • 双重检查:先不加锁快速返回,只有第一次访问时才需要加锁,减少性能损耗。
  • 线程安全std::lock_guard保证在作用域结束时自动解锁。
  • 懒初始化:实例在首次调用时才创建。

然而,若编译器不遵循C++内存模型,或者在旧的编译器上,instance 的写操作可能未被其他线程看到,导致线程安全问题。

2. C++11 std::call_oncestd::once_flag

C++11 提供了更可靠的单次初始化机制:

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(flag, []() {
            instance.reset(new Singleton());
        });
        return *instance;
    }

private:
    Singleton() = default;
    static std::unique_ptr <Singleton> instance;
    static std::once_flag flag;
};

std::unique_ptr <Singleton> Singleton::instance;
std::once_flag Singleton::flag;

优势

  • 原子性std::call_once 确保闭包只被调用一次。
  • 性能:在后续访问时无需锁定。
  • 简洁:不必手动维护互斥锁。

3. 局部静态变量(Meyers 单例)

C++11 之后,局部静态变量的初始化是线程安全的。利用这一点,我们可以进一步简化:

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;   // C++11 保证线程安全
        return instance;
    }

private:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

注意

  • 懒加载:第一次调用 getInstance() 时实例才会被创建。
  • 不可拷贝/赋值:防止外部复制单例。
  • 销毁顺序:实例在程序结束时按逆序销毁,若存在依赖,需谨慎。

4. 线程安全的懒加载与销毁

在多进程/多线程环境中,单例的销毁顺序可能导致“静态销毁顺序问题”。可以通过在 std::shared_ptr 中使用自定义删除器来避免:

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        static std::shared_ptr <Singleton> instance(
            new Singleton(),
            [](Singleton* ptr) { delete ptr; } // 自定义删除器
        );
        return instance;
    }
    // ...
};

5. 总结

方法 线程安全性 性能 代码量
双重检查锁 需要注意内存模型,易错 最优(加锁次数少) 适中
std::call_once 原子安全 较好 适中
局部静态变量 原子安全 最佳 简洁

在现代 C++(C++11 及以后)项目中,局部静态变量是最推荐的实现方式:代码最简洁,且语言层面已保证线程安全。若需要在类外释放资源或实现更细粒度控制,可考虑 std::call_once 或双重检查锁。

记住:单例模式虽然方便,但过度使用会导致代码耦合度提高,建议只在真正需要全局唯一实例时使用。

发表评论