我已将其归结为一个简单的自包含示例。主线程将1000个项目排队,并且工作线程尝试同时出列。 ThreadSanitizer抱怨在其中一个元素的读取和写入之间存在竞争,即使有一个获取 - 释放内存屏障序列保护它们。
with tf.Session() as sess:
tf.initialize_all_variables().run()
for i in range(100):
for (x, y) in zip(trX, trY):
sess.run(train_op, feed_dict={X: x, Y: y})
for (x, y) in zip(trX, trY):
estimates = sess.run(y_model, feed_dict={X: x, Y: y})
print('estimates: ', estimates)
ThreadSanitizer输出:
#include <atomic>
#include <thread>
#include <cassert>
struct FakeQueue
{
int items[1000];
std::atomic<int> m_enqueueIndex;
int m_dequeueIndex;
FakeQueue() : m_enqueueIndex(0), m_dequeueIndex(0) { }
void enqueue(int x)
{
auto tail = m_enqueueIndex.load(std::memory_order_relaxed);
items[tail] = x; // <- element written
m_enqueueIndex.store(tail + 1, std::memory_order_release);
}
bool try_dequeue(int& x)
{
auto tail = m_enqueueIndex.load(std::memory_order_acquire);
assert(tail >= m_dequeueIndex);
if (tail == m_dequeueIndex)
return false;
x = items[m_dequeueIndex]; // <- element read -- tsan says race!
++m_dequeueIndex;
return true;
}
};
FakeQueue q;
int main()
{
std::thread th([&]() {
int x;
for (int i = 0; i != 1000; ++i)
q.try_dequeue(x);
});
for (int i = 0; i != 1000; ++i)
q.enqueue(i);
th.join();
}
命令行:
==================
WARNING: ThreadSanitizer: data race (pid=17220)
Read of size 4 at 0x0000006051c0 by thread T1:
#0 FakeQueue::try_dequeue(int&) /home/cameron/projects/concurrentqueue/tests/tsan/issue49.cpp:26 (issue49+0x000000402bcd)
#1 main::{lambda()#1}::operator()() const <null> (issue49+0x000000401132)
#2 _M_invoke<> /usr/include/c++/5.3.1/functional:1531 (issue49+0x0000004025e3)
#3 operator() /usr/include/c++/5.3.1/functional:1520 (issue49+0x0000004024ed)
#4 _M_run /usr/include/c++/5.3.1/thread:115 (issue49+0x00000040244d)
#5 <null> <null> (libstdc++.so.6+0x0000000b8f2f)
Previous write of size 4 at 0x0000006051c0 by main thread:
#0 FakeQueue::enqueue(int) /home/cameron/projects/concurrentqueue/tests/tsan/issue49.cpp:16 (issue49+0x000000402a90)
#1 main /home/cameron/projects/concurrentqueue/tests/tsan/issue49.cpp:44 (issue49+0x000000401187)
Location is global 'q' of size 4008 at 0x0000006051c0 (issue49+0x0000006051c0)
Thread T1 (tid=17222, running) created by main thread at:
#0 pthread_create <null> (libtsan.so.0+0x000000027a67)
#1 std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) <null> (libstdc++.so.6+0x0000000b9072)
#2 main /home/cameron/projects/concurrentqueue/tests/tsan/issue49.cpp:41 (issue49+0x000000401168)
SUMMARY: ThreadSanitizer: data race /home/cameron/projects/concurrentqueue/tests/tsan/issue49.cpp:26 FakeQueue::try_dequeue(int&)
==================
ThreadSanitizer: reported 1 warnings
g ++版本:5.3.1
任何人都可以了解为什么tsan认为这是一场数据竞赛?
更新
这似乎是误报。为了安抚ThreadSanitizer,我添加了注释(有关受支持的注释,请参阅here,并为示例添加here)。请注意,检测是否通过宏在GCC中启用了tsan only recently been added,因此我现在必须手动将g++ -std=c++11 -O0 -g -fsanitize=thread issue49.cpp -o issue49 -pthread
传递给g ++。
-D__SANITIZE_THREAD__
现在ThreadSanitizer在运行时很开心。
答案 0 :(得分:5)
ThreadSanitizer不擅长计数,它无法理解对项目的写入总是在读取之前发生。
ThreadSanitizer可以发现m_enqueueIndex
的商店在加载之前发生,但它不知道items[m_dequeueIndex]
的商店必须在加载tail > m_dequeueIndex
之前发生。
答案 1 :(得分:5)
这看起来像https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78158。拆解由GCC生成的二进制文件表明它没有检测O0上的原子操作。 作为一种解决方法,您可以使用带有-O1 / -O2的GCC构建代码,或者为自己创建一个新的Clang构建并使用它来运行ThreadSanitizer(这是推荐的方式,因为TSan是作为Clang的一部分开发的,只有后向GCC)。
以上评论无效:TSan可以很容易地理解代码中原子之间发生的关系(可以通过在Clang中的TSan下运行上面的复制器来检查)。
我也不建议使用AnnotateHappensBefore()/ AnnotateHappensAfter()有两个原因:
在大多数情况下你不应该需要它们;他们表示代码正在做一些非常复杂的事情(在这种情况下,你可能需要仔细检查你做得对吗);
如果您在无锁代码中出错,请使用注释进行喷涂可能会掩盖该错误,以便TSan不会注意到它。