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

在多线程环境下,单例模式的实现需要保证:

  1. 只创建一次实例;
  2. 对所有线程可见且可安全访问;
  3. 采用最小的锁开销。

1. 经典实现:Meyers Singleton(C++11 之后)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance; // 局部静态变量
        return instance;
    }
    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton()  = default;
    ~Singleton() = default;
};

C++11 标准保证局部静态变量在第一次使用时是线程安全的,编译器会自动插入一次性初始化锁。因此,上述实现无需显式锁,性能最优。

2. 双重检查锁(DCL)实现(兼容 C++98)

class Singleton {
public:
    static Singleton* instance() {
        Singleton* tmp = instance_;
        if (!tmp) {
            std::lock_guard<std::mutex> lock(mutex_);
            tmp = instance_;
            if (!tmp) {
                tmp = new Singleton();
                instance_ = tmp;
            }
        }
        return tmp;
    }
    // ...
private:
    Singleton()  = default;
    ~Singleton() = default;
    static Singleton* instance_;
    static std::mutex mutex_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

双重检查锁避免了在每次访问时都加锁,但必须确保 instance_ 的可见性。C++11 引入了 std::atomic,可以进一步保证可见性:

static std::atomic<Singleton*> instance_{nullptr};

3. 使用 std::call_once

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(flag_, []{ instance_ = new Singleton(); });
        return *instance_;
    }
    // ...
private:
    Singleton()  = default;
    ~Singleton() = default;
    static Singleton* instance_;
    static std::once_flag flag_;
};

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;

std::call_oncestd::once_flag 组合提供了线程安全的单次初始化,且实现细节隐藏,代码更简洁。

4. 总结

  • C++11 以上:推荐使用 Meyers Singleton,代码最简洁、性能最佳。
  • C++98:可以使用双重检查锁或 std::call_once(若提供线程库)。
  • 可见性:如果使用原始指针,建议配合 std::atomicvolatile,以避免编译器优化导致的可见性问题。
  • 销毁:单例一般不需要显式销毁,程序结束时自动释放;若需要显式销毁,需额外实现析构逻辑。

通过上述方法,你可以在多线程 C++ 程序中安全地实现单例模式,既满足线程安全,又保持代码简洁。

发表评论