可以使用 C11 栅栏来推断来自其他线程的写入吗?
Possible to use C11 fences to reason about writes from other threads?
Adve and Gharachorloo's report,在图 4b 中,提供了以下程序示例,该程序在缺乏顺序一致性的情况下表现出意外行为:
我的问题是,是否可以只使用 C11 栅栏和 memory_order_relaxed
加载和存储,以确保 register1,如果写入,将写入值 1。这可能很难的原因抽象的保证是 P1、P2 和 P3 可能位于病态 NUMA 网络中的不同点,属性 P2 在 P3 之前看到 P1 的写入,但不知何故 P3 很快看到 P2 的写入。就 C11 规范而言,这可能难以保证的原因是 P1 对 A 的写入和 P2 对 A 的读取彼此不同步,因此规范的第 5.1.2.4.26 段将导致未定义的行为.可能我可以通过宽松的原子 fetch/store 来回避未定义的行为,但我仍然不知道如何对 P3 看到的顺序进行传递推理。
下面是一个 MWE 试图解决栅栏问题的方法,但我不确定它是否正确。我特别担心释放栅栏不够好,因为它不会刷新 p1 的存储缓冲区,只会刷新 p2 的。但是,如果您可以证明仅基于 C11 标准断言永远不会失败(与人们可能拥有的有关特定编译器和体系结构的其他信息相反),它将回答我的问题。
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <threads.h>
atomic_int a = ATOMIC_VAR_INIT(0);
atomic_int b = ATOMIC_VAR_INIT(0);
void
p1(void *_ignored)
{
atomic_store_explicit(&a, 1, memory_order_relaxed);
}
void
p2(void *_ignored)
{
if (atomic_load_explicit(&a, memory_order_relaxed)) {
atomic_thread_fence(memory_order_release); // not good enough?
atomic_store_explicit(&b, 1, memory_order_relaxed);
}
}
void
p3(void *_ignored)
{
int register1 = 1;
if (atomic_load_explicit(&b, memory_order_relaxed)) {
atomic_thread_fence(memory_order_acquire);
register1 = atomic_load_explicit(&a, memory_order_relaxed);
}
assert(register1 != 0);
}
int
main()
{
thrd_t t1, t2, t2;
thrd_create(&t1, p1, NULL);
thrd_create(&t2, p2, NULL);
thrd_create(&t3, p3, NULL);
thrd_join(&t1, NULL);
thrd_join(&t2, NULL);
thrd_join(&t3, NULL);
}
你忘记了 memory_order_acquire
围栏 p3
:
void
p3(void *_ignored)
{
int register1 = 1;
if (atomic_load_explicit(&b, memory_order_relaxed)) {
atomic_thread_fence(memory_order_acquire); // <-- Here
register1 = atomic_load_explicit(&a, memory_order_relaxed);
}
assert(register1 != 0);
}
使用此围栏,在 p2
中加载 a
与在 [=12= 中加载 a
具有 happens-before 关系].
C11 标准保证 read-read 一致性,这意味着 p3
中的加载应遵守 相同或后续修改 ,这是通过 p2
中的 happened-before 加载观察到的。因为 p2
中的加载观察 p1
中的存储,并且在您的场景中不可能对 a
进行 后续 修改,所以加载 p3
也应该观察存储在 p1
.
所以你的断言永远不会触发。
参考标准中相应的语句:
5.1.2.4 p.25: The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.
因此,根据定义,原子 访问不能包含数据竞争。
5.1.2.4 p.22: ... if a value computation A of an atomic object M happens before a value computation B of M, and the value computed by A corresponds to the value stored by side effect X, then the value computed by B shall either equal the value computed by A, or be the value stored by side effect Y, where Y follows X in the modification order of M.
下一段说,这是 缓存一致性保证。 C++11 标准更具体,并以类似的措辞说明 read-read cache coherence。
Adve and Gharachorloo's report,在图 4b 中,提供了以下程序示例,该程序在缺乏顺序一致性的情况下表现出意外行为:
我的问题是,是否可以只使用 C11 栅栏和 memory_order_relaxed
加载和存储,以确保 register1,如果写入,将写入值 1。这可能很难的原因抽象的保证是 P1、P2 和 P3 可能位于病态 NUMA 网络中的不同点,属性 P2 在 P3 之前看到 P1 的写入,但不知何故 P3 很快看到 P2 的写入。就 C11 规范而言,这可能难以保证的原因是 P1 对 A 的写入和 P2 对 A 的读取彼此不同步,因此规范的第 5.1.2.4.26 段将导致未定义的行为.可能我可以通过宽松的原子 fetch/store 来回避未定义的行为,但我仍然不知道如何对 P3 看到的顺序进行传递推理。
下面是一个 MWE 试图解决栅栏问题的方法,但我不确定它是否正确。我特别担心释放栅栏不够好,因为它不会刷新 p1 的存储缓冲区,只会刷新 p2 的。但是,如果您可以证明仅基于 C11 标准断言永远不会失败(与人们可能拥有的有关特定编译器和体系结构的其他信息相反),它将回答我的问题。
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <threads.h>
atomic_int a = ATOMIC_VAR_INIT(0);
atomic_int b = ATOMIC_VAR_INIT(0);
void
p1(void *_ignored)
{
atomic_store_explicit(&a, 1, memory_order_relaxed);
}
void
p2(void *_ignored)
{
if (atomic_load_explicit(&a, memory_order_relaxed)) {
atomic_thread_fence(memory_order_release); // not good enough?
atomic_store_explicit(&b, 1, memory_order_relaxed);
}
}
void
p3(void *_ignored)
{
int register1 = 1;
if (atomic_load_explicit(&b, memory_order_relaxed)) {
atomic_thread_fence(memory_order_acquire);
register1 = atomic_load_explicit(&a, memory_order_relaxed);
}
assert(register1 != 0);
}
int
main()
{
thrd_t t1, t2, t2;
thrd_create(&t1, p1, NULL);
thrd_create(&t2, p2, NULL);
thrd_create(&t3, p3, NULL);
thrd_join(&t1, NULL);
thrd_join(&t2, NULL);
thrd_join(&t3, NULL);
}
你忘记了 memory_order_acquire
围栏 p3
:
void
p3(void *_ignored)
{
int register1 = 1;
if (atomic_load_explicit(&b, memory_order_relaxed)) {
atomic_thread_fence(memory_order_acquire); // <-- Here
register1 = atomic_load_explicit(&a, memory_order_relaxed);
}
assert(register1 != 0);
}
使用此围栏,在 p2
中加载 a
与在 [=12= 中加载 a
具有 happens-before 关系].
C11 标准保证 read-read 一致性,这意味着 p3
中的加载应遵守 相同或后续修改 ,这是通过 p2
中的 happened-before 加载观察到的。因为 p2
中的加载观察 p1
中的存储,并且在您的场景中不可能对 a
进行 后续 修改,所以加载 p3
也应该观察存储在 p1
.
所以你的断言永远不会触发。
参考标准中相应的语句:
5.1.2.4 p.25: The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.
因此,根据定义,原子 访问不能包含数据竞争。
5.1.2.4 p.22: ... if a value computation A of an atomic object M happens before a value computation B of M, and the value computed by A corresponds to the value stored by side effect X, then the value computed by B shall either equal the value computed by A, or be the value stored by side effect Y, where Y follows X in the modification order of M.
下一段说,这是 缓存一致性保证。 C++11 标准更具体,并以类似的措辞说明 read-read cache coherence。