多线程无锁访问shared_ptr时operator=正常但reset触发coredump原因问询
问题根因分析
核心前提:你的代码本身存在未定义行为
多个线程并发读写同一个非原子
std::shared_ptr对象本身就是数据竞争,属于C++标准定义的未定义行为,无论用operator=还是reset都不是合规的写法,operator=运行正常仅为编译器实现带来的巧合,不代表代码是安全的。
两种操作的执行顺序差异(以libstdc++实现为例)
崩溃的核心原因是reset和operator=对旧资源的释放时机完全相反:
string_ptr = std::make_shared<...>的执行顺序- 先执行
std::make_shared构造新的字符串对象和对应控制块,生成一个临时shared_ptr,新对象引用计数为1 - 赋值操作优先完成内部指针/控制块的替换:将
string_ptr的内部指向切换为新的临时对象的内容 - 最后才对原来的旧对象引用计数减1,若计数归零则释放旧对象
这种场景下,哪怕读线程在替换瞬间获取指针,要么拿到未释放的旧指针,要么拿到新指针,刚好命中释放后访问的概率极低,所以你测试时没有触发崩溃。
- 先执行
string_ptr.reset(new ...)的执行顺序- 先执行
new std::string(...)构造新的字符串对象,拿到裸指针 - 进入
reset逻辑后,先对string_ptr持有的旧对象引用计数减1,若计数归零则立刻释放旧对象 - 最后才将
string_ptr的内部指针替换为新的裸指针,绑定新的控制块
这里存在非常明显的空窗期:步骤2到步骤3之间,string_ptr内部持有的是已经被释放的野指针,此时读线程如果刚好调用get()获取指针并解引用,就会直接触发野内存访问,导致coredump,这也是你可以稳定复现崩溃的原因。
- 先执行
正确的多线程使用方案
如果要并发读写shared_ptr,请选择以下任意一种合规方案:
- 所有对
shared_ptr的读写操作都加互斥锁保护 - 使用C++20引入的
std::atomic_shared_ptr,原子读写操作是线程安全的 - 读线程优先拷贝生成局部
shared_ptr副本再使用(注意拷贝操作本身也需要和写操作同步,否则依然是数据竞争)
内容的提问来源于stack exchange,提问作者oyjh
相关产品推荐
相关产品推荐

