c - 可以使用 C11 栅栏来推理其他线程的写入吗?

标签 c c11 memory-model memory-barriers memory-fences

Adve and Gharachorloo's report图 4b 中提供了以下程序示例,该程序在缺乏顺序一致性的情况下表现出意外行为:

enter image description here

我的问题是,是否可以仅使用 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);
}

最佳答案

您忘记了p3中的memory_order_acquire栅栏:

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 将与在 p2 中加载 a 处于发生之前关系p3

C11 标准保证读-读一致性,这意味着 p3 中的加载应该遵守相同或后续修改,即通过 p2 中的发生之前加载观察到。因为 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 标准更加具体,并以类似的措辞描述了读-读缓存一致性。

关于c - 可以使用 C11 栅栏来推理其他线程的写入吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34190774/

相关文章:

c - C 结构中是否有最大数量的位字段条目?

c - IFF_UP 和 IFF_RUNNING 有什么区别?

c - 哪个是最标准的 : strnlen or strnlen_s?

c - 为什么这个通用表达式会出错?

c++ - C - 使用 PThreads 时更快地锁定整数

c - C 中的 Memset 使用 Sizeof 运算符 :

c - 在 malloc 的结构中初始化原子标志

C++ 内存模型 - 此示例是否包含数据竞争?

c++ - std::mutex 会创建栅栏吗?

java - 将字段设置为 `volatile` 是否可以防止并发情况下的所有内存可见性问题?