如何从deleter重置自引用unique_ptr并避免Watcher线程空指针解引用
问题分析与修复方案
问题根源
你提到的在重置unique_ptr前先停止Watcher线程是完全可行的,也是解决这个空指针问题的正确方案,现有代码触发错误的核心原因有2个:
- 析构时序存在竞态:
Main默认析构函数会直接析构watcher成员,在Watcher析构函数执行到stop = true之前,子线程仍可能调用main.event(),此时watcher已经处于析构流程中,访问watcher->eventInfo()属于未定义行为。 - 线程同步缺失:
stop是普通bool类型,多线程下的读写没有同步保障,子线程可能感知不到stop状态的更新,导致线程持续运行。
修复代码
#include <iostream> #include <memory> #include <thread> #include <atomic> struct Main { struct Watcher { Watcher(Main &main) : main(main) , th(std::thread(&Watcher::func, this)) { } void func() { while (!stop.load(std::memory_order_acquire)) { main.event(); } } int eventInfo() { return 0; } // 新增主动停止接口 void stop() { stop.store(true, std::memory_order_release); if (th.joinable()) { th.join(); } } ~Watcher() { // 析构时兜底停止,避免外部忘记调用 stop(); } Main &main; std::thread th; std::atomic<bool> stop{}; // 改为原子类型保证线程可见性 }; Main() : watcher(std::make_unique<Watcher>(*this)) { } // 自定义析构函数,控制停止时序 ~Main() { if (watcher) { // 先停止线程,确保子线程不再调用event watcher->stop(); // 线程完全停止后再释放Watcher对象 watcher.reset(); } } void event() { // 这里可以保留判空做双重保险,正常时序下不会走到空分支 if (watcher) { std::cout << watcher->eventInfo() << "\n"; } } std::unique_ptr<Watcher> watcher; };
修复逻辑说明
- 主动控制析构时序:
Main析构时先调用watcher->stop()等待子线程完全退出,保证后续不会再有event回调触发,再释放Watcher对象,从根源上避免访问处于析构状态的watcher。 - 线程安全保障:将
stop改为std::atomic<bool>,搭配内存序保证多线程下状态更新的可见性,避免数据竞争。 - 双重兜底:
Watcher析构函数仍保留停止逻辑,避免外部场景下忘记主动调用stop()导致的资源泄漏。
内容的提问来源于stack exchange,提问作者patraulea
相关产品推荐
相关产品推荐

