C11 中非原子的栅栏
Fences with non-atomics in C11
有什么方法可以使用栅栏来推理 C11 中非原子操作的行为吗?具体来说,我想在某些字段需要 int
s 以与旧接口兼容的情况下确保代码安全,这些旧接口可能会读取数据结构并将其写入文件或将它们作为系统调用参数传递。由于没有要求 atomic_int
甚至与 int
大小相同,我不能使用 atomic_int
.
这是一个最小的工作示例,不幸的是根据第 5.1.2.4 节第 25 段产生了未定义的行为,因为 ready
:
上的数据争用
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>
int ready; /* purposely NOT _Atomic */
int value;
void
p1()
{
value = 1;
atomic_thread_fence(memory_order_release);
ready = 1;
}
void
p2(void *_ignored)
{
while (!ready)
;
atomic_thread_fence(memory_order_acquire);
printf("%d\n", value);
}
int
main()
{
thrd_t t;
thrd_create(&t, p2, NULL);
p1();
thrd_join(&t, NULL);
}
我的具体问题是是否可以修复上述代码以保证在不将 ready
更改为 _Atomic
的情况下打印 1
。 (我可以将 ready
设为 volatile
,但在规范中看不到任何这会有所帮助的建议。)
一个相关的问题是编写上面的代码是否安全,因为我的代码 运行 所在的任何机器都具有缓存一致性?我知道 many things go wrong 当 C11 程序包含所谓的良性竞争时,所以我真的在寻找一个合理的编译器和架构可以对上述代码做什么的细节,而不是关于数据竞争的一般警告和未定义的行为。
Is there any way to use fences to reason about the behavior of non-atomic operations in C11?
您使用栅栏的方式是正确的,但如果您希望能够推理程序行为,
您有责任确保 store(1) 到 ready
之间存在严格的线程间修改顺序
和它的负载(1)。这通常是 atomic
变量发挥作用的地方。
根据 C11 标准,您在 ready
上有一个数据竞争(正如您所指出的),并且您可以预料到未定义的行为。
My specific question is whether it's possible to fix the above code to guarantee printing 1 without changing ready to an _Atomic.
(I could make ready a volatile, but don't see any suggestion in the spec that this would help.)
符合标准的答案是 'no',并且由于该标准不支持您的案例,因此您不会在此上下文中找到与 volatile
相关的任何内容。
但是,考虑到其中一个目标是支持与许多体系结构的兼容性,该标准是故意严格的。
这并不意味着数据竞赛总是会导致每个平台出现问题。
虽然在共享上下文中使用非原子类型的问题很棘手。
人们有时认为,如果 CPU 对 int
等类型的操作是不可分的,则可以将其用作 atomic_int
的替代品。
这是不正确的,因为 'atomic' 是一个具有更广泛分支的概念:
不可分割read/writes - 这些适用于许多平台上的常规类型。
有限的优化 - 编译器转换确实会以许多意想不到的方式导致未定义的行为。
编译器可能会重新排序内存操作,将一个变量与另一个变量组合在同一内存位置,
从循环中删除变量,将其保存在寄存器中,等等...
您可以通过声明您的变量 volatile
来防止大部分这种情况,因为它限制了编译器可以进行的优化。
核心之间的数据同步 - 在您的情况下,这是在存储和负载之间 ready
上存在严格的线程间排序的情况下由栅栏处理的。
有了真正的atomic_int
,你可以使用轻松的操作。
您的代码是否有效取决于平台和编译器,但至少声明 ready
标志 volatile
。
我用 gcc -O3
编译器优化在 X86_64 上做了一个测试 运行,没有 volatile
它陷入了无限循环。
比较编译器为原子和非原子情况发出的指令之间的差异也是一个好主意。
A related question is whether it's safe to write the above code anyway, because any machine my code will run on has cache coherence?
您绝对需要缓存一致性,因为众所周知,不支持它的系统很难编程。如果没有缓存一致性,您编写它的方式几乎肯定无法工作。
有什么方法可以使用栅栏来推理 C11 中非原子操作的行为吗?具体来说,我想在某些字段需要 int
s 以与旧接口兼容的情况下确保代码安全,这些旧接口可能会读取数据结构并将其写入文件或将它们作为系统调用参数传递。由于没有要求 atomic_int
甚至与 int
大小相同,我不能使用 atomic_int
.
这是一个最小的工作示例,不幸的是根据第 5.1.2.4 节第 25 段产生了未定义的行为,因为 ready
:
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>
int ready; /* purposely NOT _Atomic */
int value;
void
p1()
{
value = 1;
atomic_thread_fence(memory_order_release);
ready = 1;
}
void
p2(void *_ignored)
{
while (!ready)
;
atomic_thread_fence(memory_order_acquire);
printf("%d\n", value);
}
int
main()
{
thrd_t t;
thrd_create(&t, p2, NULL);
p1();
thrd_join(&t, NULL);
}
我的具体问题是是否可以修复上述代码以保证在不将 ready
更改为 _Atomic
的情况下打印 1
。 (我可以将 ready
设为 volatile
,但在规范中看不到任何这会有所帮助的建议。)
一个相关的问题是编写上面的代码是否安全,因为我的代码 运行 所在的任何机器都具有缓存一致性?我知道 many things go wrong 当 C11 程序包含所谓的良性竞争时,所以我真的在寻找一个合理的编译器和架构可以对上述代码做什么的细节,而不是关于数据竞争的一般警告和未定义的行为。
Is there any way to use fences to reason about the behavior of non-atomic operations in C11?
您使用栅栏的方式是正确的,但如果您希望能够推理程序行为,
您有责任确保 store(1) 到 ready
之间存在严格的线程间修改顺序
和它的负载(1)。这通常是 atomic
变量发挥作用的地方。
根据 C11 标准,您在 ready
上有一个数据竞争(正如您所指出的),并且您可以预料到未定义的行为。
My specific question is whether it's possible to fix the above code to guarantee printing 1 without changing ready to an _Atomic. (I could make ready a volatile, but don't see any suggestion in the spec that this would help.)
符合标准的答案是 'no',并且由于该标准不支持您的案例,因此您不会在此上下文中找到与 volatile
相关的任何内容。
但是,考虑到其中一个目标是支持与许多体系结构的兼容性,该标准是故意严格的。 这并不意味着数据竞赛总是会导致每个平台出现问题。
虽然在共享上下文中使用非原子类型的问题很棘手。
人们有时认为,如果 CPU 对 int
等类型的操作是不可分的,则可以将其用作 atomic_int
的替代品。
这是不正确的,因为 'atomic' 是一个具有更广泛分支的概念:
不可分割read/writes - 这些适用于许多平台上的常规类型。
有限的优化 - 编译器转换确实会以许多意想不到的方式导致未定义的行为。 编译器可能会重新排序内存操作,将一个变量与另一个变量组合在同一内存位置, 从循环中删除变量,将其保存在寄存器中,等等... 您可以通过声明您的变量
volatile
来防止大部分这种情况,因为它限制了编译器可以进行的优化。核心之间的数据同步 - 在您的情况下,这是在存储和负载之间
ready
上存在严格的线程间排序的情况下由栅栏处理的。 有了真正的atomic_int
,你可以使用轻松的操作。
您的代码是否有效取决于平台和编译器,但至少声明 ready
标志 volatile
。
我用 gcc -O3
编译器优化在 X86_64 上做了一个测试 运行,没有 volatile
它陷入了无限循环。
比较编译器为原子和非原子情况发出的指令之间的差异也是一个好主意。
A related question is whether it's safe to write the above code anyway, because any machine my code will run on has cache coherence?
您绝对需要缓存一致性,因为众所周知,不支持它的系统很难编程。如果没有缓存一致性,您编写它的方式几乎肯定无法工作。