我正在学习不同的内存顺序。
我有这样的代码,它可以工作并通过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围栏完全相同。