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

在多线程环境下实现单例模式,需要确保在并发访问时只创建一个实例,同时避免竞态条件和性能瓶颈。下面详细介绍几种常见实现方式及其优缺点,并给出完整可编译的示例代码。


1. 懒汉式(线程不安全)

最直观的做法是使用静态指针并在第一次访问时延迟初始化。

class Singleton {
private:
    Singleton() {}
public:
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }
private:
    static Singleton* instance;
};
Singleton* Singleton::instance = nullptr;

缺点:在多线程情况下,两个线程可能同时进入 if (!instance) 并各自创建实例,导致资源浪费甚至破坏单例。


2. 懒汉式 + 双重检查锁(DCL)

使用互斥锁配合双重检查,减少锁开销。

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

优点:只有第一次创建时才加锁,后续访问几乎不受锁影响。
缺点:实现复杂,容易出现细节错误(如指令重排导致的可见性问题)。


3. 静态局部变量(C++11 线程安全)

C++11 引入了线程安全的局部静态变量初始化。

class Singleton {
private:
    Singleton() {}
public:
    static Singleton& getInstance() {
        static Singleton instance; // 第一次调用时初始化,线程安全
        return instance;
    }
    // 禁止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点:代码简洁、无锁,线程安全且延迟初始化。
缺点:无法在单例销毁时执行自定义逻辑(除非使用 std::atexit)。


4. Meyer’s Singleton(单例实现者)

与静态局部变量相同,只是命名更符合单例设计模式。

class Singleton {
public:
    static Singleton& Instance() {
        static Singleton instance;
        return instance;
    }
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

5. 基于 std::call_once 的实现

使用 std::once_flagstd::call_once 可以在多线程环境中确保仅执行一次初始化逻辑。

class Singleton {
private:
    Singleton() {}
    static std::unique_ptr <Singleton> instance;
    static std::once_flag flag;
public:
    static Singleton& getInstance() {
        std::call_once(flag, [](){ instance.reset(new Singleton()); });
        return *instance;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
std::unique_ptr <Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::flag;

优点:实现简单且线程安全,适用于需要动态分配的单例。


何时使用哪种实现?

场景 推荐实现 说明
需要延迟初始化、且 C++11+ 静态局部变量 / Meyer’s 最简洁、无锁、线程安全
需要自定义销毁顺序 std::call_once + unique_ptr 可在 unique_ptr 析构时控制
对性能极致敏感,且已知单例在多线程下仅创建一次 双重检查锁(DCL) 适合极端性能场景,但实现繁琐
旧编译器不支持 C++11 双重检查锁(DCL)或手动锁 兼容旧环境,但需注意指令重排

代码完整示例(C++17)

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

class Logger {
public:
    static Logger& Instance() {
        static Logger instance; // C++11+ 线程安全
        return instance;
    }
    void log(const std::string& msg) {
        std::lock_guard<std::mutex> lock(mtx_);
        std::cout << msg << std::endl;
    }
private:
    Logger() {}
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    std::mutex mtx_;
};

int main() {
    // 多线程测试
    std::thread t1([](){ Logger::Instance().log("Thread 1"); });
    std::thread t2([](){ Logger::Instance().log("Thread 2"); });
    t1.join(); t2.join();
    return 0;
}

总结:在 C++ 中实现线程安全单例最推荐的方式是利用 C++11 引入的局部静态变量,代码简洁且性能优秀。若需要更细粒度的控制(如自定义销毁顺序),std::call_oncestd::unique_ptr 也是非常稳妥的方案。始终记得禁用拷贝构造和赋值操作,避免出现多实例。

发表评论