为什么线程清理器会抱怨 acquire/release 线程栅栏?

Why does the thread sanitizer complain about acquire/release thread fences?

我正在了解不同的内存顺序。

我有这段代码,可以使用 passes GCC's and Clang's thread sanitizers:

#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 就不需要 'acquire' 加载,所以我决定使用 'relaxed' 加载,然后是 'acquire'围栏.

我希望它能工作,但它是 rejected by the thread sanitizers,它声称并发 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

Atomic-fence synchronization

An atomic release operation X in thread A synchronizes-with an acquire fence F in thread B, if

  • there exists an atomic read Y (with any memory order)
  • Y reads the value written by X (or by the release sequence headed by X)
  • Y is sequenced-before F in thread B

In this case, all non-atomic and relaxed atomic stores that are sequenced-before X in thread A will happen-before all non-atomic and relaxed atomic loads from the same locations made in thread B after F.

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

线程清理器当前 doesn't support 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 fences are not affected, the code is still rejected if I use such a fence, they still need to be annotated with __tsan_acquire+__tsan_release, exactly the same as acq-rel fences