19

我正在学习不同的记忆顺序。

我有这段代码,它可以工作并通过 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,那么“获取”加载是不必要的,所以我决定使用“放松”加载,然后是“获取”围栏。

我希望它能够工作,但它被 thread sanitizers 拒绝,它声称 concurrentstate++是数据竞争。

#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 为首的释放序列)
  • Y 在线程 B 中排在 F 之前

在这种情况下,在线程 A 中的 X 之前排序的所有非原子和松弛原子存储将发生在 F 之后线程 B 中相同位置的所有非原子和松弛原子加载之前。

据我了解,所有条件都满足:

  • “存在原子读取 Y(具有任何内存顺序)” - 检查:a.load(std::memory_order_relaxed)
  • “Y 读取 X 写入的值”——检查,它从 读取值a.store(to, std::memory_order_release);
  • “Y 在线程 B 中排在 F 之前”——检查。
4

1 回答 1

16

线程清理程序当前不支持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 栅栏完全相同

于 2021-12-31T15:42:59.353 回答