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

单例模式(Singleton)是软件设计模式之一,用于保证一个类只有一个实例,并提供全局访问点。虽然单例的概念很简单,但在多线程环境中实现线程安全却是一门学问。下面以 C++11 及以上标准为基础,给出几种常见且高效的线程安全实现方式,并说明它们的优缺点。


1. Meyer’s 单例(局部静态变量)

class ThreadSafeSingleton {
public:
    static ThreadSafeSingleton& instance() {
        static ThreadSafeSingleton instance;   // C++11 保证初始化线程安全
        return instance;
    }
    // 禁止复制和移动
    ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
    ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
    ThreadSafeSingleton(ThreadSafeSingleton&&) = delete;
    ThreadSafeSingleton& operator=(ThreadSafeSingleton&&) = delete;

private:
    ThreadSafeSingleton() = default;
    ~ThreadSafeSingleton() = default;
};
  • 优点:实现最简洁,编译器/运行时自动保证初始化线程安全;不存在显式锁。
  • 缺点:无法控制对象销毁顺序(尤其是跨库退出时),且无法延迟加载(除非使用 std::call_once 包装)。

2. std::call_once + std::unique_ptr

class ThreadSafeSingleton {
public:
    static ThreadSafeSingleton& instance() {
        std::call_once(initFlag, []() {
            instancePtr.reset(new ThreadSafeSingleton);
        });
        return *instancePtr;
    }

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

private:
    ThreadSafeSingleton() = default;
    ~ThreadSafeSingleton() = default;

    static std::unique_ptr <ThreadSafeSingleton> instancePtr;
    static std::once_flag initFlag;
};

std::unique_ptr <ThreadSafeSingleton> ThreadSafeSingleton::instancePtr = nullptr;
std::once_flag ThreadSafeSingleton::initFlag;
  • 优点:明确控制实例创建和销毁,避免了静态变量的全局析构问题。
  • 缺点:代码略显繁琐,性能略低于 Meyer’s 单例(额外的 call_once 机制)。

3. 双重检查锁(Double-Check Locking)

在 C++11 之前,双重检查锁常被用来延迟初始化单例。但在 C++11 之后,使用 std::atomic 可以保证可见性,下面给出一种较为安全的实现:

class ThreadSafeSingleton {
public:
    static ThreadSafeSingleton& instance() {
        ThreadSafeSingleton* tmp = instancePtr.load(std::memory_order_acquire);
        if (!tmp) {
            std::lock_guard<std::mutex> lock(initMutex);
            tmp = instancePtr.load(std::memory_order_relaxed);
            if (!tmp) {
                tmp = new ThreadSafeSingleton;
                instancePtr.store(tmp, std::memory_order_release);
            }
        }
        return *tmp;
    }

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

private:
    ThreadSafeSingleton() = default;
    ~ThreadSafeSingleton() = default;

    static std::atomic<ThreadSafeSingleton*> instancePtr;
    static std::mutex initMutex;
};

std::atomic<ThreadSafeSingleton*> ThreadSafeSingleton::instancePtr(nullptr);
std::mutex ThreadSafeSingleton::initMutex;
  • 优点:延迟初始化,避免不必要的锁开销。
  • 缺点:实现复杂,容易出现错误(尤其是内存可见性问题),不如 std::call_once 简单可靠。

4. 现代 C++ 方案:std::shared_ptr + std::call_once

如果单例需要在多处被共享引用,或者需要手动控制引用计数,下面的方案很合适:

class ThreadSafeSingleton {
public:
    static std::shared_ptr <ThreadSafeSingleton> instance() {
        std::call_once(initFlag, []() {
            instancePtr = std::shared_ptr <ThreadSafeSingleton>(new ThreadSafeSingleton, &deleter);
        });
        return instancePtr;
    }

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

private:
    ThreadSafeSingleton() = default;
    ~ThreadSafeSingleton() = default;

    static void deleter(ThreadSafeSingleton* p) {
        delete p;
    }

    static std::shared_ptr <ThreadSafeSingleton> instancePtr;
    static std::once_flag initFlag;
};

std::shared_ptr <ThreadSafeSingleton> ThreadSafeSingleton::instancePtr = nullptr;
std::once_flag ThreadSafeSingleton::initFlag;
  • 优点:可共享实例,允许外部显式释放;兼容 shared_ptr 的生命周期管理。
  • 缺点:需要注意 deleter 的实现,避免双重删除。

小结

  1. Meyer’s 单例是最常用且最简洁的实现方式,适用于大多数情况。
  2. std::call_once+unique_ptrshared_ptr 可用于需要更细粒度控制对象生命周期的场景。
  3. 双重检查锁在 C++11 之后需要使用原子操作保证可见性,使用时务必小心。
  4. 对于跨线程、跨进程的共享数据,建议使用更高层次的同步原语(如 std::mutexstd::shared_mutex)配合单例,避免单例成为并发瓶颈。

根据项目的具体需求与代码风格,选择最合适的实现方案即可。祝你编码愉快!

发表评论