多线程场景下弱指针未过期时的安全更新方法问询
问题
客户端持有指向线程安全对象(示例中用double代替)的std::shared_ptr,服务器在独立线程中通过std::weak_ptr对该对象执行更新操作。服务器原本的逻辑是先调用expired()检查weak_ptr是否过期,未过期则执行更新,但担心在expired()检查和更新操作的间隙,客户端的shared_ptr可能被销毁,导致访问非法内存。现寻求无需自定义原子变量或互斥量的实现方式,仅借助shared_ptr自带的原子计数器,确保检查和更新操作能在对象销毁前安全执行。
原代码示例
服务器代码
struct server { public: static void subscribe(const std::shared_ptr<double>& d) { m_values.safe_push_back(d); //线程安全的vector插入操作 } void update() { auto updater = [](std::weak_ptr<double>& wd) { if(wd.expired()) wd = nullptr; else *wd += 2.0; //此处存在线程安全隐患? }; m_values.safe_remove_all(nullptr); m_values.safe_visit(updater); }; private: static safe_vector<std::weak_ptr<double>> m_values; }
客户端代码
struct client { void subcribe_sleep_print(const double random_seconds) { std::shared_ptr<double> d = std::make_shared<double>(0.0); //示例中先创建对象再订阅 server::subscribe(d); sleep_for_seconds(random_seconds); //随机休眠若干秒 std::cout << *d << std::endl; } }
注:实际场景中使用的是线程安全对象,
+=操作本身是线程安全的。
解决方案
核心思路是通过**将weak_ptr原子性升级为shared_ptr**来消除竞态条件:std::weak_ptr::lock()方法会一次性完成「检查对象存活状态」和「增加引用计数」两个动作——如果对象未销毁,升级成功后引用计数加1,确保持有新shared_ptr期间对象不会被销毁;如果对象已销毁,lock()会返回空的shared_ptr。
修改后的服务器更新代码
void update() { auto updater = [](std::weak_ptr<double>& wd) { if(auto sp = wd.lock()) //原子性升级为shared_ptr,彻底避免竞态 { *sp += 2.0; //此时对象必然存活,操作安全 } else { wd = nullptr; //对象已销毁,标记以便后续清理 } }; m_values.safe_remove_all(nullptr); m_values.safe_visit(updater); };
原理说明
wd.lock()是线程安全的原子操作,完全规避了expired()检查与后续操作之间的时间窗口问题。- 只要
sp这个临时shared_ptr的生命周期未结束,原对象的引用计数就不会降到0——哪怕客户端此时销毁了自己的shared_ptr,对象也会因为sp的存在继续存活,直到更新操作完成。 - 当
sp离开作用域时,引用计数自动递减,若此时没有其他shared_ptr持有对象,对象才会被销毁。
这种实现完全依赖shared_ptr自带的线程安全引用计数机制,不需要额外的互斥量或自定义原子变量,就能安全完成检查与更新操作。
内容的提问来源于stack exchange,提问作者SpeakX
相关产品推荐
相关产品推荐

