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

在多线程环境下,单例模式需要保证两个重要属性:

  1. 全局唯一性 – 仅有一个实例。
  2. 线程安全 – 同一时间只有一个线程能够创建实例,其余线程要么等待,要么直接获取已创建的实例。

下面给出几种常见实现方式,并说明其优缺点。


1. 双重检查锁(Double-Checked Locking,DCL)

class Singleton {
public:
    static Singleton& instance() {
        if (!ptr_) {                // 第一次检查
            std::lock_guard<std::mutex> lock(mtx_);
            if (!ptr_) {            // 第二次检查
                ptr_ = new Singleton();
            }
        }
        return *ptr_;
    }
private:
    Singleton() = default;
    ~Singleton() { delete ptr_; }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* ptr_;
    static std::mutex mtx_;
};

Singleton* Singleton::ptr_ = nullptr;
std::mutex Singleton::mtx_;

优点

  • 第一次创建后,后续访问不需要加锁,性能好。
  • 在大多数现代编译器(GCC、Clang、MSVC)和 C++11 以上的内存模型下可以安全使用。

缺点

  • 代码略显复杂,易出错。
  • 在旧编译器或使用 std::atomic 但没有 std::memory_order_acquire/release 的情况下仍可能出现重排序导致的可见性问题。

2. Meyers 单例(函数内部静态局部变量)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton obj;   // C++11 之后保证线程安全
        return obj;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点

  • 代码最简洁。
  • C++11 标准保证局部静态变量的初始化是线程安全的。
  • 不需要手动释放,生命周期由程序结束自动管理。

缺点

  • 在某些嵌入式环境或特殊编译器实现中,初始化可能是懒加载,导致第一次调用性能略低。
  • 对于需要自定义销毁顺序的情况,控制权不够细致。

3. std::call_once + std::unique_ptr

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag_, [](){
            instancePtr_ = std::unique_ptr <Singleton>(new Singleton());
        });
        return *instancePtr_;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::once_flag initFlag_;
    static std::unique_ptr <Singleton> instancePtr_;
};

std::once_flag Singleton::initFlag_;
std::unique_ptr <Singleton> Singleton::instancePtr_;

优点

  • 只需要一次初始化,内部使用 std::once_flag 保证原子性。
  • 结合 unique_ptr 可自动管理实例生命周期。
  • 兼容 C++03 只需使用 Boost 的 boost::call_once

缺点

  • 对于极端高并发场景,std::call_once 可能在某些实现中会略微慢于静态局部变量的初始化。

4. 对象池实现(延迟销毁)

如果单例需要在程序结束前显式销毁,或在多进程共享内存中使用,可以把单例包装在对象池里:

class Singleton {
public:
    static Singleton& get() {
        static SingletonPool pool;
        return pool.instance();
    }
private:
    struct SingletonPool {
        SingletonPool() { inst = new Singleton(); }
        ~SingletonPool() { delete inst; }
        Singleton* instance() { return inst; }
        Singleton* inst;
    };
    // ...
};

选型建议

场景 推荐实现
C++11+ 简单应用 Meyers 单例
对销毁顺序有特殊需求 std::call_once + unique_ptr
旧编译器不支持 C++11 双重检查锁(DCL)配合 std::atomic
嵌入式系统,资源极限 静态局部变量(Meyers)
多进程共享内存 对象池 + 映射

小结

C++ 的标准库在 2011 年之后提供了足够的原语(std::mutex, std::call_once, std::atomic)来实现线程安全的单例。最推荐的方式是 Meyers 单例,因为其代码最简洁、性能最优,并且在现代编译器下天然支持线程安全。若需更细粒度的控制(如销毁顺序、跨进程共享),可以考虑 std::call_once 或对象池实现。无论哪种方式,记得 删除拷贝构造和赋值运算符,以保持全局唯一性。

发表评论