在多线程环境下,单例模式的实现需要保证只有一个实例存在,同时在并发访问时不产生竞态条件。下面给出几种常见的实现方式,并讨论它们的优缺点。
-
局部静态变量(C++11 之后)
class Singleton { public: static Singleton& getInstance() { static Singleton instance; // 局部静态,编译器保证线程安全 return instance; } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; private: Singleton() = default; };- 优点:代码简洁,依赖标准库实现,几乎不需要额外同步。
- 缺点:无法控制实例的销毁时机;若在多线程环境下第一次访问
getInstance()时异常抛出,可能导致后续调用失败。
-
双重检查锁(双检锁)
class Singleton { public: static Singleton* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mtx); if (instance == nullptr) { instance = new Singleton(); } } return instance; } private: Singleton() = default; static Singleton* instance; static std::mutex mtx; }; Singleton* Singleton::instance = nullptr; std::mutex Singleton::mtx;- 优点:延迟初始化,避免了不必要的同步。
- 缺点:实现复杂,容易出错。需要使用
std::atomic或其他内存序保证可见性,否则在某些体系结构上会出现可见性或重排序问题。
-
Meyers Singleton 与
std::call_onceclass Singleton { public: static Singleton& getInstance() { std::call_once(flag, [](){ instance.reset(new Singleton); }); return *instance; } private: Singleton() = default; static std::unique_ptr <Singleton> instance; static std::once_flag flag; }; std::unique_ptr <Singleton> Singleton::instance; std::once_flag Singleton::flag;- 优点:
std::call_once在所有实现中都保证一次且仅一次调用,线程安全且性能优秀。 - 缺点:仍然无法精确控制销毁时机。
- 优点:
-
基于
std::shared_ptr的懒加载class Singleton { public: static std::shared_ptr <Singleton> getInstance() { static std::shared_ptr <Singleton> ptr{new Singleton}; return ptr; } private: Singleton() = default; };- 优点:共享指针会在所有引用计数为零时自动销毁实例。
- 缺点:多线程下仍需保证首次创建的线程安全,通常与
std::call_once结合使用。
小结
- 对于绝大多数现代C++代码,局部静态变量(Meyers Singleton)已经足够,既简洁又安全。
- 如果你需要更细粒度的控制(比如在应用程序退出前手动销毁实例),可以考虑
std::unique_ptr+std::once_flag的组合。 - 双重检查锁虽然可以避免不必要的锁,但由于实现细节复杂,容易导致未定义行为,建议尽量避免。
通过上述方法,你可以在C++多线程环境中安全、高效地实现单例模式。