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

在多线程环境下,单例模式需要保证对象只被创建一次,同时确保所有线程都能安全地访问该实例。下面介绍几种常见的实现方式,并给出完整代码示例。

1. 经典 Meyers 单例

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

C++11 起,函数内部的 static 对象在首次调用时初始化,且初始化过程是线程安全的。只要编译器支持 C++11 或更高标准,就可以直接使用这种方式。

2. 显式互斥锁

如果你想兼容 C++98 或者需要更细粒度的控制,可以使用 std::mutex

#include <mutex>

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(initFlag, [](){ instance.reset(new Singleton); });
        return *instance;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr <Singleton> instance;
    static std::once_flag initFlag;
};

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

这里 std::call_once 确保 instance 只被初始化一次,避免多线程竞争。

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

双重检查锁是一种性能优化的做法,但在 C++ 中实现时需要注意 std::atomicvolatile

#include <atomic>

class Singleton {
public:
    static Singleton* getInstance() {
        Singleton* tmp = instance.load(std::memory_order_acquire);
        if (!tmp) {
            std::lock_guard<std::mutex> lock(mtx);
            tmp = instance.load(std::memory_order_relaxed);
            if (!tmp) {
                tmp = new Singleton;
                instance.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::atomic<Singleton*> instance;
    static std::mutex mtx;
};

std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;

需要确保对 instance 的访问使用合适的内存序,避免编译器优化导致的不可预期行为。

4. 静态成员对象(模块化单例)

如果单例需要在不同编译单元共享,可以将实例放在独立的源文件:

// Singleton.h
class Singleton {
public:
    static Singleton& instance();
    void doSomething();
private:
    Singleton() = default;
    ~Singleton() = default;
};

// Singleton.cpp
#include "Singleton.h"

Singleton& Singleton::instance() {
    static Singleton s;
    return s;
}

void Singleton::doSomething() {
    // ...
}

因为 static 对象在 instance() 函数内部,编译器会处理线程安全。

小结

  • C++11 及以上:使用 Meyers 单例最简洁且安全。
  • 兼容旧标准:使用 std::call_oncestd::mutex 的显式锁。
  • 性能优化:双重检查锁需配合 std::atomic 与正确的内存序。

选择哪种实现方式取决于项目对标准支持、性能需求以及代码可读性的考虑。只要遵循以上原则,线程安全的单例模式就能轻松实现。

发表评论