【发布时间】:2018-03-10 01:27:21
【问题描述】:
请看下面的代码:
#include <pthread.h>
#include <boost/atomic.hpp>
class ReferenceCounted {
public:
ReferenceCounted() : ref_count_(1) {}
void reserve() {
ref_count_.fetch_add(1, boost::memory_order_relaxed);
}
void release() {
if (ref_count_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete this;
}
}
private:
boost::atomic<int> ref_count_;
};
void* Thread1(void* x) {
static_cast<ReferenceCounted*>(x)->release();
return NULL;
}
void* Thread2(void* x) {
static_cast<ReferenceCounted*>(x)->release();
return NULL;
}
int main() {
ReferenceCounted* obj = new ReferenceCounted();
obj->reserve(); // for Thread1
obj->reserve(); // for Thread2
obj->release(); // for the main()
pthread_t t[2];
pthread_create(&t[0], NULL, Thread1, obj);
pthread_create(&t[1], NULL, Thread2, obj);
pthread_join(t[0], NULL);
pthread_join(t[1], NULL);
}
这有点类似于Boost.Atomic 中的Reference counting 示例。
主要区别在于嵌入的ref_count_在构造函数中被初始化为1(一旦构造函数完成,我们就有一个对ReferenceCounted对象的引用)并且代码不使用@987654331 @。
请不要责怪我在代码中使用了delete this - 这是我在大型代码库中使用的模式,我现在对此无能为力。
现在使用来自主干的clang 3.5(详情如下)和ThreadSanitizer(tsan v2)编译的这段代码导致ThreadSanitizer的以下输出:
WARNING: ThreadSanitizer: data race (pid=9871)
Write of size 1 at 0x7d040000f7f0 by thread T2:
#0 operator delete(void*) <null>:0 (a.out+0x00000004738b)
#1 ReferenceCounted::release() /home/A.Romanek/tmp/tsan/main.cpp:15 (a.out+0x0000000a2c06)
#2 Thread2(void*) /home/A.Romanek/tmp/tsan/main.cpp:29 (a.out+0x0000000a2833)
Previous atomic write of size 4 at 0x7d040000f7f0 by thread T1:
#0 __tsan_atomic32_fetch_sub <null>:0 (a.out+0x0000000896b6)
#1 boost::atomics::detail::base_atomic<int, int, 4u, true>::fetch_sub(int, boost::memory_order) volatile /home/A.Romanek/tmp/boost/boost_1_55_0/boost/atomic/detail/gcc-atomic.hpp:499 (a.out+0x0000000a3329)
#2 ReferenceCounted::release() /home/A.Romanek/tmp/tsan/main.cpp:13 (a.out+0x0000000a2a71)
#3 Thread1(void*) /home/A.Romanek/tmp/tsan/main.cpp:24 (a.out+0x0000000a27d3)
Location is heap block of size 4 at 0x7d040000f7f0 allocated by main thread:
#0 operator new(unsigned long) <null>:0 (a.out+0x000000046e1d)
#1 main /home/A.Romanek/tmp/tsan/main.cpp:34 (a.out+0x0000000a286f)
Thread T2 (tid=9874, running) created by main thread at:
#0 pthread_create <null>:0 (a.out+0x00000004a2d1)
#1 main /home/A.Romanek/tmp/tsan/main.cpp:40 (a.out+0x0000000a294e)
Thread T1 (tid=9873, finished) created by main thread at:
#0 pthread_create <null>:0 (a.out+0x00000004a2d1)
#1 main /home/A.Romanek/tmp/tsan/main.cpp:39 (a.out+0x0000000a2912)
SUMMARY: ThreadSanitizer: data race ??:0 operator delete(void*)
==================
ThreadSanitizer: reported 1 warnings
奇怪的是,thread T1 在对引用计数器进行原子递减时,会将大小为 1 的写入与thread T2 相同的内存位置。
前一种写法怎么解释?是ReferenceCounted 类的析构函数进行了一些清理吗?
这是误报吗?还是代码错了?
我的设置是:
$ uname -a
Linux aromanek-laptop 3.13.0-29-generic #53-Ubuntu SMP Wed Jun 4 21:00:20 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
$ clang --version
Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5)
Target: x86_64-pc-linux-gnu
Thread model: posix
代码编译如下:
clang++ main.cpp -I/home/A.Romanek/tmp/boost/boost_1_55_0 -pthread -fsanitize=thread -O0 -g -ggdb3 -fPIE -pie -fPIC
请注意,在我的机器上,boost::atomic<T> 的实现解析为 __atomic_load_n 系列函数,即 ThreadSanitizer claims to understand。
更新 1:使用 clang 3.4 最终版本时也会发生同样的情况。
更新 2:-std=c++11 和 <atomic> 与 libstdc++ 和 libc++ 出现相同的问题。
【问题讨论】:
-
我不知道 Boost 是什么意思,因为 C++ 标准没有说明任何关于 Boost 的内容,但如果这些是
std原子,那么std::atomic_thread_fence只会与其他原子操作同步如果有合适的中间访问。有关详细信息,请参阅 [atomic.fences]。 -
尝试将栅栏改为
ref_count_.fetch_add(0, boost::memory_order_acquire)。 -
@KerrekSB,确实,将栅栏更改为您建议的
fetch_add()会使 tsan 安静下来。但是,我不确定这是否只是 tsan 的一种解决方法,或者它实际上修复了代码中的竞争。 -
在下面查看我的 cmets。围栏按某些规则行事。如果你愿意,你可以推理规则,或者你可以不使用栅栏。 Fences 是一个糟糕的原语,因为您不想在某些特定对象上进行同步是非常不寻常的。
-
看看 std::atomic 是否报告同样的问题会很有趣。
标签: c++ multithreading boost reference-counting thread-sanitizer