2

Adve 和 Gharachorloo 的报告在图 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 段将导致未定义的行为. 可能我可以通过轻松的原子获取/存储来回避未定义的行为,但我仍然不知道如何对 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);
}
4

1 回答 1

2

你忘记了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);
}

有了这个栅栏,加载将与加载a发生之前发生关系。p2ap3

C11 标准保证read-read coherence,这意味着加载p3应该遵守相同或后续修改,这是通过发生之前加载来观察的p2。因为 loading inp2观察 store in p1,并且在您的场景中不可能进行后续修改,所以 loading in也应该观察 storage in 。ap3p1

所以你的断言永远不会触发。


参考标准中的相应声明

5.1.2.4 p.25:如果程序的执行包含不同线程中的两个冲突操作,则程序的执行包含数据竞争,其中至少一个不是原子的,并且两者都没有在另一个之前发生。任何此类数据竞争都会导致未定义的行为。

因此,根据定义,原子访问不能包含数据竞争。

5.1.2.4 p.22: ... 如果原子对象 M 的值计算 A 发生在 M 的值计算 B 之前,并且 A 计算的值对应于副作用 X 存储的值,则计算的值由B 应该等于 A 计算的值,或者是副作用 Y 存储的值,其中 Y 按照 M 的修改顺序跟随 X。

下一段说,这是缓存一致性保证。C++11 标准更为具体,并以类似的措辞说明了读-读缓存一致性

于 2015-12-10T09:55:28.333 回答