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

在 C++ 中实现单例模式(Singleton)时,最常见的挑战之一就是保证多线程环境下的线程安全。下面将通过几种常见方案来实现线程安全的单例,并比较它们的优缺点。


1. 经典的双重检查锁(Double-Check Locking)

#include <mutex>

class Singleton {
public:
    static Singleton& instance() {
        if (!ptr) {                         // 第一次检查(无锁)
            std::lock_guard<std::mutex> lock(mtx);
            if (!ptr) {                     // 第二次检查(加锁)
                ptr = new Singleton();
            }
        }
        return *ptr;
    }

    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

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

优点

  • 仅在第一次访问时创建实例,后续访问不需要加锁,性能较好。

缺点

  • new 的顺序保证依赖编译器和硬件实现(C++11 的 std::atomic 与内存模型可解决)。
  • 代码较为冗长,易出错。

2. 局部静态变量(Meyers Singleton)

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

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
};

优点

  • 代码简洁,易于维护。
  • C++11 以后编译器保证局部静态变量初始化是线程安全的。

缺点

  • 对销毁顺序有一定要求(如果在 atexit 时出现循环依赖,可能导致段错误)。

3. std::call_oncestd::once_flag

#include <mutex>

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag, [](){ ptr = new Singleton(); });
        return *ptr;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    static Singleton* ptr;
    static std::once_flag initFlag;
};

Singleton* Singleton::ptr = nullptr;
std::once_flag Singleton::initFlag;

优点

  • 明确表示一次性初始化,语义清晰。
  • 兼容旧标准(C++03 通过 Boost 实现)。

缺点

  • 仍需要手动管理 ptr 的销毁。

4. 通过 std::shared_ptr 自动销毁

#include <memory>
#include <mutex>

class Singleton {
public:
    static std::shared_ptr <Singleton> instance() {
        std::call_once(initFlag, [](){ ptr = std::make_shared <Singleton>(); });
        return ptr;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    static std::shared_ptr <Singleton> ptr;
    static std::once_flag initFlag;
};

std::shared_ptr <Singleton> Singleton::ptr = nullptr;
std::once_flag Singleton::initFlag;

优点

  • 自动管理内存,避免手动 delete。
  • 支持多线程安全的构造和销毁。

缺点

  • std::shared_ptr 产生一定的性能开销,尤其在高频访问场景中。

5. 现代 C++ 方案:std::unique_ptr 与懒加载

class Singleton {
public:
    static Singleton& instance() {
        static std::unique_ptr <Singleton> instance{
            std::make_unique <Singleton>()
        };
        return *instance;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
};

优点

  • 结合 unique_ptr 与局部静态变量,既安全又高效。
  • 对象销毁时机与程序结束一致。

小结

  • Meyers Singleton(局部静态变量)是最推荐的方案,代码最简洁且线程安全。
  • 对于需要显式销毁或更复杂生命周期管理的情况,可考虑 std::call_once + std::shared_ptrunique_ptr
  • 在旧编译器或 C++11 前标准下,std::call_oncestd::once_flag 是最安全的实现方式。

实践建议:除非有特殊需求(如需要在单例构造时做复杂逻辑,或需要在运行时手动销毁),否则直接使用 Meyers Singleton。如果担心销毁顺序问题,可在 atexit 里手动销毁 std::shared_ptr 管理的实例。

发表评论