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

在多线程环境下,保证单例实例的线程安全是设计模式中的一个重要挑战。下面介绍几种在C++中实现线程安全单例的常用方法,并讨论它们的优缺点。

1. Meyers 单例(C++11 之后的线程安全局部静态变量)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;  // C++11 保证线程安全
        return instance;
    }
private:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
  • 优点:实现最简洁,编译器负责初始化,符合 C++11 的线程安全局部静态变量语义。
  • 缺点:如果你需要在销毁时执行特定逻辑,C++11 标准并不保证析构顺序;在某些嵌入式环境下,可能不支持 C++11。

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

class Singleton {
public:
    static Singleton* getInstance() {
        if (!instance_) {
            std::lock_guard<std::mutex> lock(mtx_);
            if (!instance_) {
                instance_ = new Singleton();
            }
        }
        return instance_;
    }
private:
    Singleton() = default;
    static Singleton* instance_;
    static std::mutex mtx_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mtx_;
  • 优点:在第一次创建实例后,后续访问不需要锁,性能相对较好。
  • 缺点:实现细节比较繁琐;在 C++11 之前的编译器中,可能由于内存可见性问题导致错误;需要使用 std::atomicvolatile 来保证可见性。

3. 静态局部变量加 std::call_once

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(initFlag_, []{
            instance_ = new Singleton();
        });
        return *instance_;
    }
private:
    Singleton() = default;
    static Singleton* instance_;
    static std::once_flag initFlag_;
};

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
  • 优点std::call_once 由标准库实现,线程安全且易于理解。
  • 缺点:同样需要手动销毁(如通过 std::unique_ptr 或在 atexit 注册),如果不销毁会导致资源泄漏。

4. 用 std::shared_ptrstd::make_shared

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        static std::shared_ptr <Singleton> instance = std::make_shared<Singleton>();
        return instance;
    }
private:
    Singleton() = default;
};
  • 优点:使用 std::shared_ptr 自动管理生命周期,线程安全性与局部静态变量相同。
  • 缺点:在极端情况下,std::shared_ptr 的引用计数操作可能会产生额外开销。

5. 枚举实现(Java 风格,C++ 并不适用)

C++ 中不支持使用枚举实现单例,因其枚举不能包含成员函数。

何时选择哪种实现?

场景 推荐实现
只需要单例,不需要自定义析构 Meyers 单例(局部静态)
需要在销毁时执行特定逻辑 std::call_oncestd::unique_ptr
需要在旧编译器下兼容 双重检查锁 + std::atomic
需要对实例进行计数或共享 std::shared_ptr

小结

C++11 之后,最推荐使用的是 Meyers 单例(局部静态变量),因为它实现最简单、最可靠,且符合标准库的线程安全保证。若对销毁顺序有严格要求,可考虑 std::call_oncestd::unique_ptr。在旧环境或特殊需求下,双重检查锁和 std::shared_ptr 仍是可行的替代方案。

发表评论