指向const对象的shared_ptr线程安全性及锁保护需求咨询
场景与代码
现有场景:线程持有指向const map<int, string>对象的shared_ptr,该shared_ptr会被其他线程偶尔更新,同时也会被不同线程读取。代码实现如下:
class SharedPointerHolder { private: shared_ptr<const map<int, string>> sptr_; public: SharedPointerHolder() : sptr_(make_shared<const map<int, string>>()) {}; // new_sptr is guaranteed to be a valid pointer. void UpdateSharedPointer(shared_ptr<const map<int, string>>&& new_sptr) { if (!new_sptr) { return; } sptr_ = move(new_sptr); } void ReadSharedPointer() { // Since only UpdateSharedPointer() updates sptr_, it is guaranteed that sptr_ will always be valid. Still just in case, I have added this "if". if (!sptr_) { return; } auto local_sptr = sptr_; // Read the map object pointed by the local_sptr and perform some operations. } };
疑问
- 当前
UpdateSharedPointer()不会被多线程同时调用,请问sptr_是否需要通过锁保护?本人了解到锁用于保护shared_ptr指向的数据,而本次数据为const类型,且可接受读取脏数据,认为无需锁。 - 若
UpdateSharedPointer()可能被多线程同时调用,除了脏数据容忍度外,是否存在其他需关注的竞态条件?核心担忧是数据损坏问题。
解答
1. 单线程调用UpdateSharedPointer时的锁需求
需要加锁,核心原因不是脏数据,而是普通shared_ptr的读写不同步会触发未定义行为:
shared_ptr内部包含对象指针和控制块指针(存储引用计数等信息),其赋值/移动赋值操作并非原子操作——修改sptr_时会分步骤更新这两个指针。如果读线程刚好在写线程修改的间隙读取sptr_,可能拿到一个内部状态不一致的shared_ptr(比如对象指针是新的,但控制块还是旧的),后续对这个shared_ptr的操作(如拷贝、访问对象)会导致不可预测的错误,甚至程序崩溃。- 你提到可接受脏数据(读取旧的
map实例),加锁完全不影响这个需求:锁只会保证读取到的sptr_是完整的(要么是旧的完整实例,要么是新的完整实例),不会出现中间损坏的状态。如果使用C++20及以后的atomic<shared_ptr<T>>存储sptr_,也可以通过原子操作实现线程安全,无需手动加锁。
2. 多线程调用UpdateSharedPointer时的额外风险
当UpdateSharedPointer()被多线程同时调用时,除了脏数据问题,还存在严重的数据竞争导致的sptr_损坏风险:
- 多个线程并发执行
sptr_ = move(new_sptr)时,会同时修改sptr_的内部状态,直接触发数据竞争,属于未定义行为。可能出现旧控制块的引用计数未正确递减,引发内存泄漏;或者引用计数被错误修改,导致map对象被提前释放,后续访问出现野指针。 - 即使能接受脏数据,这种场景下读线程也可能拿到完全无效的
shared_ptr,直接导致程序崩溃。
因此这种情况下必须用锁(比如std::mutex)保护sptr_的所有读写操作,确保同一时间只有一个线程修改sptr_,且读取操作不会与修改操作重叠。
内容的提问来源于stack exchange,提问作者prashant
相关产品推荐
相关产品推荐

