在多线程环境下,单例模式的实现需要保证只有一个实例,并且实例的创建过程是线程安全的。C++20提供了几个特性,使得实现更加简洁、安全和高效。
-
使用
std::call_once和std::once_flag
std::call_once会在多线程调用时保证其内部函数只执行一次,而std::once_flag用于跟踪状态。结合std::unique_ptr可以实现懒加载单例,代码示例如下:#include <memory> #include <mutex> class Singleton { public: static Singleton& instance() { std::call_once(initFlag_, []() { instancePtr_ = std::unique_ptr <Singleton>(new Singleton()); }); return *instancePtr_; } // 禁止拷贝和移动 Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; Singleton(Singleton&&) = delete; Singleton& operator=(Singleton&&) = delete; void doSomething() { /* 业务逻辑 */ } private: Singleton() = default; ~Singleton() = default; static std::once_flag initFlag_; static std::unique_ptr <Singleton> instancePtr_; }; std::once_flag Singleton::initFlag_; std::unique_ptr <Singleton> Singleton::instancePtr_ = nullptr;该实现的优点是:
- 线程安全:
std::call_once内部使用了原子操作,确保单例被安全创建。 - 懒加载:只有在第一次调用
instance()时才会实例化。 - 避免双重检查锁定:传统的 double‑check lock 在 C++98/11 中存在可见性问题,而
std::call_once已经内部完成了正确的同步。
- 线程安全:
-
使用 C++20 的
std::atomic<std::shared_ptr<>>
如果需要支持多线程对单例的读写,并且希望实现更细粒度的锁控制,可以采用原子共享指针。示例:#include <memory> #include <atomic> class Singleton { public: static std::shared_ptr <Singleton> instance() { auto ptr = instance_.load(std::memory_order_acquire); if (!ptr) { std::lock_guard<std::mutex> lock(mutex_); ptr = instance_.load(std::memory_order_relaxed); if (!ptr) { ptr = std::shared_ptr <Singleton>(new Singleton()); instance_.store(ptr, std::memory_order_release); } } return ptr; } void doSomething() { /* 业务逻辑 */ } private: Singleton() = default; ~Singleton() = default; static std::atomic<std::shared_ptr<Singleton>> instance_; static std::mutex mutex_; }; std::atomic<std::shared_ptr<Singleton>> Singleton::instance_ = nullptr; std::mutex Singleton::mutex_;这里的实现兼顾了原子性与锁的使用,保证了实例创建的可见性,同时避免了不必要的锁竞争。
-
利用 C++20 的
std::atomic_ref与std::shared_mutex
对于需要频繁读取但偶尔写入的单例,std::shared_mutex可以提供读写分离。结合std::atomic_ref可以进一步减少内存层面的竞争。代码略显复杂,但在高并发读场景下会有性能提升。 -
注意单例的销毁
在多线程程序退出时,如果单例使用了new创建的裸指针,可能导致析构顺序问题。推荐使用std::unique_ptr或std::shared_ptr来管理生命周期,或者让单例成为函数内静态局部对象(C++11 之下已保证线程安全的构造):Singleton& Singleton::instance() { static Singleton instance; return instance; }这种写法最简洁且无锁,但无法在单例需要动态销毁或自定义内存管理时使用。
总结
std::call_once与std::once_flag是最简单、最安全的方式。- 对于需要细粒度控制或多线程读写并发的情况,可考虑
std::atomic<std::shared_ptr<>>或std::shared_mutex。 - 始终关注实例销毁顺序和资源管理,避免内存泄漏或悬挂引用。
通过以上方法,你可以在 C++20 项目中安全、高效地实现单例模式,满足多线程环境下的需求。