我把它归结为一个简单的自包含例子。主线程排队1000个项目,并且工作线程尝试同时出队。 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说种族! ++ 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(); }ThreadSanitizer输出:
================== 警告:ThreadSanitizer:data race(pid = 17220)阅读大小4在0x0000006051c0由线程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) 以前由主线程写入0x0000006051c0的大小4:#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) 位置是大小为4008的全局'q'在0x0000006051c0(issue49 + 0x0000006051c0) 主线程创建的线程T1(tid = 17222,运行):#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) 摘要:ThreadSanitizer :data race /home/cameron/projects/concurrentqueue/tests/tsan/issue49.cpp:26 FakeQueue :: try_dequeue(int&) ================= = ThreadSanitizer:report 1 warnings命令行:
g ++ -std = c ++ 11 -O0 -g -fsanitize = thread issue49.cpp -o issue49 -pthreadg ++版本:5.3.1
任何人都可以了解为什么an是数据竞赛?
$ b更新 b
这似乎是一个假阳性。为了安装ThreadSanitizer,我添加了注释(参见此处为支持的此处为例)。请注意,通过宏检测是否在GCC中启用了tsan,具有最近才添加 a>,所以我不得不手动传递 -D__SANITIZE_THREAD __ 到g ++。
#if defined(__ SANITIZE_THREAD__) #define TSAN_ENABLED #elif defined(__ has_feature) #if __has_feature(thread_sanitizer) #define TSAN_ENABLED #endif #endif #ifdef TSAN_ENABLED #define TSAN_ANNOTATE_HAPPENS_BEFORE(addr)\ AnnotateHappensBefore(__ FILE__,__LINE__,(void *)(addr)) #define TSAN_ANNOTATE_HAPPENS_AFTER(addr)\ AnnotateHappensAfter(__ FILE__,__LINE__,(void *)(addr)) externCvoid AnnotateHappensBefore(const char * f,int l,void * addr); externCvoid AnnotateHappensAfter(const char * f,int l,void * addr); #else #define TSAN_ANNOTATE_HAPPENS_BEFORE(addr) #define TSAN_ANNOTATE_HAPPENS_AFTER(addr) #endif 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; TSAN_ANNOTATE_HAPPENS_BEFORE(& items [tail]); 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; TSAN_ANNOTATE_HAPPENS_AFTER(& items [m_dequeueIndex]); x = items [m_dequeueIndex]; ++ m_dequeueIndex; return true; } }; // main()和之前一样现在ThreadSanitizer很高兴运行时。
解决方案ThreadSanitizer 不利于计数,它不能理解写入项目总是发生在读取之前。
ThreadSanitizer可以发现 m_enqueueIndex 的存储在加载之前发生,但不知道 items [m_dequeueIndex] 必须在加载之前发生, tail> m_dequeueIndex 。
I've boiled this down to a simple self-contained example. The main thread enqueues 1000 items, and a worker thread tries to dequeue concurrently. ThreadSanitizer complains that there's a race between the read and the write of one of the elements, even though there is an acquire-release memory barrier sequence protecting them.
#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(); }ThreadSanitizer output:
================== 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 warningsCommand line:
g++ -std=c++11 -O0 -g -fsanitize=thread issue49.cpp -o issue49 -pthreadg++ version: 5.3.1
Can anybody shed some light onto why tsan thinks this is a data race?
UPDATE
It seems like this is a false positive. To appease ThreadSanitizer, I've added annotations (see here for the supported ones and here for an example). Note that detecting whether tsan is enabled in GCC via a macro has only recently been added, so I had to manually pass -D__SANITIZE_THREAD__ to g++ for now.
#if defined(__SANITIZE_THREAD__) #define TSAN_ENABLED #elif defined(__has_feature) #if __has_feature(thread_sanitizer) #define TSAN_ENABLED #endif #endif #ifdef TSAN_ENABLED #define TSAN_ANNOTATE_HAPPENS_BEFORE(addr) \ AnnotateHappensBefore(__FILE__, __LINE__, (void*)(addr)) #define TSAN_ANNOTATE_HAPPENS_AFTER(addr) \ AnnotateHappensAfter(__FILE__, __LINE__, (void*)(addr)) extern "C" void AnnotateHappensBefore(const char* f, int l, void* addr); extern "C" void AnnotateHappensAfter(const char* f, int l, void* addr); #else #define TSAN_ANNOTATE_HAPPENS_BEFORE(addr) #define TSAN_ANNOTATE_HAPPENS_AFTER(addr) #endif 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; TSAN_ANNOTATE_HAPPENS_BEFORE(&items[tail]); 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; TSAN_ANNOTATE_HAPPENS_AFTER(&items[m_dequeueIndex]); x = items[m_dequeueIndex]; ++m_dequeueIndex; return true; } }; // main() is as beforeNow ThreadSanitizer is happy at runtime.
解决方案The ThreadSanitizer is not good at counting, it cannot understand that writes to the items always happen before the reads.
The ThreadSanitizer can find that the stores of m_enqueueIndex happen before the loads, but it does not understand that the store to items[m_dequeueIndex] must happen before the load when tail > m_dequeueIndex.
更多推荐
为什么ThreadSanitizer报告与这个无锁示例的比赛?
发布评论