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

在多线程环境下,单例模式需要保证只有一个实例被创建,而且创建过程必须是线程安全的。下面以C++17为例,演示三种常用实现方式,并对比它们的优缺点。

1. 带锁的懒汉式

#include <mutex>

class Singleton {
public:
    static Singleton& getInstance() {
        std::lock_guard<std::mutex> lock(mtx_);
        if (!instance_) {
            instance_ = new Singleton();
        }
        return *instance_;
    }
    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

    static Singleton* instance_;
    static std::mutex mtx_;
};

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

优点

  • 代码直观易懂
  • 能在需要实例时才创建,符合懒加载需求

缺点

  • 每次访问都要获取锁,导致性能瓶颈
  • instance_ 需要手动删除,容易出现内存泄漏

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

#include <atomic>
#include <mutex>

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_;

优点

  • 只在第一次创建时锁一次,后续访问无需锁
  • 线程安全且性能相对更好

缺点

  • 代码较为复杂,易出错
  • 需要使用原子指针来保证可见性

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

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;
};

优点

  • 代码最简洁,完全不需要手动锁或原子操作
  • 语言层面保证线程安全,符合标准
  • 自动析构,避免内存泄漏

缺点

  • 对于极端延迟要求的系统,第一次调用会有初始化开销
  • 如果需要在程序退出时释放资源,需自行添加析构函数或使用 std::unique_ptr

4. 什么时候选择哪种实现?

场景 推荐实现
需要手动控制实例的生命周期,或在多模块中共享 带锁懒汉式(或双重检查锁)
代码简洁优先,且不在意第一次访问的开销 局部静态变量
在非常高并发的读场景下,写入次数极少 双重检查锁(避免锁的频繁获取)

5. 小结

  • 线程安全 是实现单例模式的核心。
  • 局部静态变量 在 C++11 之后提供了最简洁且安全的实现方式。
  • 需要注意的是,单例模式虽然方便,但过度使用会导致代码耦合度高,单元测试困难。建议在业务场景中合理评估是否真的需要单例。

发表评论