次のコードを見てください。
#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の参照カウントの例に多少似ています。
主な違いは、組み込みref_count_
がコンストラクターで初期化さ1
れること (コンストラクターが完了すると、ReferenceCounted
オブジェクトへの参照が 1 つになる) と、コードが を使用しないことboost::intrusive_ptr
です。
コードで使用したことで私を責めないでください。delete this
これは、私が職場の大規模なコード ベースで使用しているパターンであり、今のところ私にできることは何もありません。
このコードをclang 3.5
from trunk (詳細は下記) と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
私のマシンでは、 の実装は、 ThreadSanitizer が理解していると主張する一連の関数にboost::atomic<T>
解決されることに注意してください。__atomic_load_n
更新 1:clang 3.4
最終リリースを使用する場合も同じことが起こります。