高并发下单例必须显式控制销毁,唯一可靠方案是std::unique_ptr+std::call_once:前者支持运行时reset触发析构,后者保证线程安全一次性初始化;需配合shutdown()、原子标志、卸载钩子及无异常析构逻辑。

高并发场景下,单例资源不能依赖程序退出时自动析构——static局部变量的析构虽线程安全,但时机不可控,且无法应对动态库卸载、热重载、测试反复启停等真实需求。必须显式控制释放路径。
用 std::unique_ptr + std::call_once 替代裸指针或静态变量
这是唯一能兼顾线程安全初始化与可控销毁的组合方案。Meyers 单例(static Singleton instance)无法在运行时销毁;而裸指针手写 DCLP 容易因异常、重排、锁竞争出错。
-
std::unique_ptr管理堆上实例,确保析构函数可被显式触发 -
std::call_once保证初始化只执行一次,无需手动加锁,避免双重检查锁定(DCLP)的所有陷阱 - 所有资源释放逻辑必须放在
~Singleton()中,且该析构函数不得调用其他单例接口(防止析构顺序崩溃) - 示例关键片段:
class Singleton { public: static Singleton& getInstance() { std::call_once(init_flag, []{ instance = std::make_unique<Singleton>(); }); return *instance; } <pre class="brush:php;toolbar:false;">static void shutdown() { if (instance) { instance.reset(); // ✅ 触发析构 } }private: Singleton() = default; ~Singleton() { / 关闭文件、join线程、munmap等 / } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr<Singleton> instance; static std::once_flag init_flag;
};
销毁前必须检查线程状态和资源存活性
并发环境下,shutdown() 可能被多个线程同时调用,或在某个线程正访问单例时触发。硬 delete 会导致崩溃。
立即学习“C++免费学习笔记(深入)”;
- 用
std::atomic_bool m_shutting_down{false}标记销毁中状态,shutdown()开头即if (m_shutting_down.exchange(true)) return; - 若单例内含
std::thread,必须先判断joinable()再join(),禁止detach() - 若持有
std::mutex或条件变量,销毁前需确保无线程阻塞在其上;必要时用try_lock()避免死锁 - 所有系统资源(fd、socket、mmap)必须显式
close()/closesocket()/munmap(),不能依赖 RAII 对象析构——因为它们可能在单例析构之后才轮到析构
动态库场景下必须绑定卸载钩子
Linux 的 dlclose() 和 Windows 的 FreeLibrary() 不会触发 C++ 静态析构,仅靠 shutdown() 调用不保险。
- Linux:在同编译单元定义
static void __attribute__((destructor)) so_cleanup() { Singleton::shutdown(); },并确保编译时不启用-fvisibility=hidden - Windows:在
DllMain的DLL_PROCESS_DETACH分支中,仅当lpReserved == nullptr时调用Singleton::shutdown()(排除进程退出场景) - 两种钩子都只是兜底,不能替代宿主显式调用
Singleton::shutdown()——必须在文档/头文件中标明“调用dlclose()前务必先调用shutdown()”
最易被忽略的一点:单例析构函数里调用 std::cout、std::cerr 或任何可能抛异常的 STL 函数。这些在卸载阶段极易触发 std::terminate。所有日志必须走轻量级、无异常、无锁的 C 风格输出(如 write(2)),或完全禁用。


















