使用Boost intrusive_ptr时触发TSAN线程竞态报错的问题咨询
问题判定
这个TSAN报错属于误报,不存在真实的数据竞态。
你的引用计数实现逻辑是完全正确的:引用计数减1时使用memory_order_release语义,保证对象所有前置读写操作不会被重排到引用计数修改之后;检测到引用计数归零时加的memory_order_acquire屏障,会同步其他线程所有release语义的写入,确保删除对象前所有对对象的访问都已经完成,不存在访问已释放内存的风险。
换成std::shared_ptr后报错消失,本质是TSAN对标准库原子实现有专门的插桩识别,不会误判,不代表boost::intrusive_ptr的实现有问题。
报错根因
GCC 7.3.1版本内置的ThreadSanitizer无法正确识别Boost 1.72.0版本中boost::atomic的gcc_atomic底层实现:TSAN没有把boost::atomic<int>::fetch_sub识别为原子操作,反而当成了普通的4字节内存写入,因此判定后续主线程执行delete时的8字节内存写入和这个操作存在竞态,属于工具检测的漏识别问题,不是代码逻辑问题。
解决方案
你可以根据实际场景选以下任意一种方案消除误报:
- 把
Shared结构体里的boost::atomic<int>替换为std::atomic<int>,内存序枚举换成std命名空间下的对应值,TSAN可以原生识别std::atomic的原子操作,不会再误报 - 升级GCC版本到9及以上,高版本GCC配套的TSAN对Boost.Atomic的适配更完善,这类误报不会再出现
- 编译时添加宏定义
-DBOOST_ATOMIC_FORCE_LIBATOMIC,强制Boost.Atomic调用libatomic的标准原子接口,TSAN可以正确识别这类接口的原子属性 - 编写TSAN抑制文件,添加规则忽略
intrusive_ptr_release函数内delete操作和refcount_访问的竞态报告
相关环境与复现信息
- 环境版本:GCC 7.3.1,Boost 1.72.0
- 编译命令:
g++ -ggdb -I /usr/local/boost_1_72_0 -O3 -fsanitize=thread TSan_Intr_Ptr.cpp -lpthread - 复现代码:
#include <boost/smart_ptr/intrusive_ptr.hpp> #include <boost/smart_ptr/detail/spinlock.hpp> #include <boost/atomic.hpp> #include <thread> #include <iostream> #include <vector> #include <atomic> #include <unistd.h> using namespace std; struct Shared { mutable boost::atomic<int> refcount_; //From Boost official atomic usage example friend void intrusive_ptr_add_ref(const Shared * x) { x->refcount_.fetch_add(1, boost::memory_order_relaxed); } friend void intrusive_ptr_release(const Shared* x) { if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) { boost::atomic_thread_fence(boost::memory_order_acquire); delete x; } } }; vector<boost::intrusive_ptr<Shared const>> g_vec; boost::detail::spinlock g_lock = BOOST_DETAIL_SPINLOCK_INIT; void consumer() { while(true) { g_lock.lock(); g_vec.clear(); g_lock.unlock(); usleep(10); } } int main() { thread thd(consumer); while(true) { boost::intrusive_ptr<Shared const> p(new Shared); g_lock.lock(); g_vec.push_back(p); g_lock.unlock(); usleep(1); } return 0; }
- TSAN报错片段:
WARNING: ThreadSanitizer: data race (pid=14513) Write of size 8 at 0x7b0400000010 by main thread: #0 operator delete(void*) <null> (libtsan.so.0+0x00000006fae4) #1 intrusive_ptr_release(Shared const*) /Test/TSan_Intr_Ptr_Min.cpp:25 (a.out+0x000000401195) #2 boost::intrusive_ptr<Shared const>::~intrusive_ptr() /boost_1_72_0/boost/smart_ptr/intrusive_ptr.hpp:98 (a.out+0x000000401195) #3 main /x01/exch/Test/TSan_Intr_Ptr_Min.cpp:51 (a.out+0x000000401195) Previous atomic write of size 4 at 0x7b0400000010 by thread T1: #0 __tsan_atomic32_fetch_sub <null> (libtsan.so.0+0x00000006576f) #1 boost::atomics::detail::gcc_atomic_operations<4ul, true>::fetch_sub(unsigned int volatile&, unsigned int, boost::memory_order) /boost_1_72_0/boost/atomic/detail/ops_gcc_atomic.hpp:116 (a.out+0x000000401481) #2 boost::atomics::detail::base_atomic<int, int>::fetch_sub(int, boost::memory_order) volatile /usr/local/boost_1_72_0/boost/atomic/detail/atomic_template.hpp:348 (a.out+0x000000401481) #3 intrusive_ptr_release(Shared const*) /Test/TSan_Intr_Ptr_Min.cpp:22 (a.out+0x000000401481) ...
内容的提问来源于stack exchange,提问作者aKumara
相关产品推荐
相关产品推荐

