为什么线程消毒剂会抱怨获取/释放线程围栏



我正在学习不同的内存顺序。

我有这样的代码,它可以工作并通过GCC和Clang的线程清理程序:

#include <atomic>
#include <iostream>
#include <future>
    
int state = 0;
std::atomic_int a = 0;
void foo(int from, int to) 
{
    for (int i = 0; i < 10; i++)
    {
        while (a.load(std::memory_order_acquire) != from) {}
        state++;
        a.store(to, std::memory_order_release);
    }
}
int main()
{    
    auto x = std::async(std::launch::async, foo, 0, 1);
    auto y = std::async(std::launch::async, foo, 1, 0);
}

我认为,如果最终没有返回from,那么"获取"负载是不必要的,所以我决定使用"放松"负载,然后使用"获取"围栏。

我本以为它能工作,但它被线程清理程序拒绝了,线程清理程序声称并发state++是一场数据竞赛。

#include <atomic>
#include <iostream>
#include <future>
    
int state = 0;
std::atomic_int a = 0;
void foo(int from, int to) 
{
    for (int i = 0; i < 10; i++)
    {
        while (a.load(std::memory_order_relaxed) != from) {}
        std::atomic_thread_fence(std::memory_order_acquire);
        state++;
        a.store(to, std::memory_order_release);
    }
}
int main()
{    
    auto x = std::async(std::launch::async, foo, 0, 1);
    auto y = std::async(std::launch::async, foo, 1, 0);
}

为什么这是一场数据竞赛?

Cppreference说

原子围栏同步

线程A中的原子释放操作X与获取同步线程B中的栅栏F,如果

  • 存在原子读取Y(具有任何内存顺序(
  • Y读取由X(或由以X为首的释放序列(写入的值
  • 在线程B中,Y在F之前排序

在这种情况下在线程A中X之前排序将发生在所有非原子和F.之后在线程B中从相同位置产生的松弛原子载荷

据我所知,所有条件都满足:

  • "存在原子读取Y(具有任何存储器顺序("——检查:a.load(std::memory_order_relaxed)
  • "Y读取由X〃写入的值——检查,它从a.store(to, std::memory_order_release);中读取值
  • "Y在线程B〃中的F之前被排序检查

线程清理程序当前不支持std::atomic_thread_fence。(GCC和Clang使用相同的线程消毒剂,因此它适用于两者。(

GCC 12(目前为中继(对此发出警告:

atomic_base.h:133:26: warning: 'atomic_thread_fence' is not supported with '-fsanitize=thread' [-Wtsan]
  133 |   { __atomic_thread_fence(int(__m)); }
      |     ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~

为了防止消毒剂无视围栏,您可以使用__tsan_acquire__tsan_release手动对其进行检测。

#include <sanitizer/tsan_interface.h>

while (a.load(std::memory_order_relaxed) != from) {}
__tsan_acquire(&a); // <--
std::atomic_thread_fence(std::memory_order_acquire);

我认为自动确定哪些原子变量受到围栏的影响是很棘手的。

尽管错误报告说seq_cst围栏不受影响,但如果我使用这样的围栏,代码仍然会被拒绝,它们仍然需要用__tsan_acquire+__tsan_release进行注释,与acq-rel围栏完全相同。

相关内容

  • 没有找到相关文章

最新更新