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

在多线程环境下,单例模式的实现需要保证即使多个线程同时请求实例时,也只会创建一次对象并返回同一实例。下面给出几种常用的线程安全实现方式,并说明其优缺点和使用场景。


1. C++11 std::call_once + std::once_flag

std::call_once 是标准库提供的原子性一次性执行函数,结合 std::once_flag 可以非常简洁地实现线程安全单例。

#include <iostream>
#include <memory>
#include <mutex>

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

    void sayHello() const { std::cout << "Hello from Singleton!\n"; }

private:
    Singleton() { std::cout << "Singleton constructed\n"; }
    ~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;

优点

  • 代码简洁,使用标准库,线程安全保证可靠。
  • 只在第一次调用时进行初始化,后续调用几乎没有开销。

缺点

  • 需要 C++11 或更高版本。

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

C++11 之后,函数内的局部静态变量在第一次使用时会被线程安全地初始化。

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance; // 线程安全初始化
        return instance;
    }

    // 其它成员
private:
    Singleton() { std::cout << "Singleton constructed\n"; }
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点

  • 代码最简洁。
  • 初始化时自动实现线程安全。

缺点

  • 只能在 C++11 之后使用。
  • 需要手动删除拷贝构造和赋值操作符,以防止意外复制。

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

经典实现方式,适用于旧版本编译器或想完全控制锁的场景。

class Singleton {
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    ~Singleton() { delete instance; }

private:
    Singleton() { std::cout << "Singleton constructed\n"; }
    static Singleton* instance;
    static std::mutex mtx;
};

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

优点

  • 对旧编译器友好,适用于不支持 C++11 的环境。

缺点

  • 需要手动保证 instance 的可见性,使用 volatilestd::atomic
  • 代码较复杂,容易出现细节错误。

4. 静态局部变量 + std::atomic

如果你想在 C++03 环境下使用类似双重检查锁的方式,配合 std::atomic 可以实现:

#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() {}
    static std::atomic<Singleton*> instance;
    static std::mutex mtx;
};

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

优点

  • 兼容 C++03,使用标准原子操作。

缺点

  • 代码量大,维护成本高。

5. 使用 std::shared_ptr 并配合 std::make_shared

如果你需要在多处共享单例且需要自动释放资源,使用 std::shared_ptr 也可以:

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        static std::shared_ptr <Singleton> instance = std::make_shared<Singleton>();
        return instance;
    }
private:
    Singleton() {}
};

此方式与 Meyers 单例相似,C++11 后线程安全。


小结

方法 线程安全性 编译器要求 代码简洁度 适用场景
std::call_once + std::once_flag C++11+ 需要一次性初始化
Meyers 单例 C++11+ 简洁、易用
双重检查锁 C++03+ ⚠️ 旧环境、需要手动锁
std::atomic 双检查 C++03+ ⚠️ 旧环境、细粒度控制
std::shared_ptr C++11+ 需要共享所有权

在现代 C++ 开发中,推荐使用 std::call_once 或 Meyers 单例,因为它们既简洁又安全,且完全依赖标准库实现。只有在特殊需求或兼容旧编译器时,才考虑使用更复杂的双重检查锁或 std::atomic 方案。

发表评论