为什么ThreadSanitizer报告与这个无锁示例的比赛? [英] Why does ThreadSanitizer report a race with this lock-free example?

查看:421
本文介绍了为什么ThreadSanitizer报告与这个无锁示例的比赛?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我把它归结为一个简单的自包含例子。主线程排队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 -pthread 

g ++版本: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 warnings

Command line:

g++ -std=c++11 -O0 -g -fsanitize=thread issue49.cpp -o issue49 -pthread

g++ 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 before

Now 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报告与这个无锁示例的比赛?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆