使用 <stdatomic.h> 在 C11 GCC 中使数据 reads/writes 原子化?
Making data reads/writes atomic in C11 GCC using <stdatomic.h>?
我从 SO 线程 here and here 等中了解到,假设多线程应用程序中的 reads/writes 数据在 OS/hardware 级别是原子的是不安全的,并且可能会导致数据损坏。我想知道在 Linux.
上使用带有 GCC 编译器的 <stdatomic.h>
C11 库使 int
变量成为原子读写的最简单方法
如果我当前在一个线程中有一个 int
赋值:messageBox[i] = 2
,我如何使这个赋值原子化?同样适用于阅读测试,例如 if (messageBox[i] == 2)
.
对于 C11 原子,您甚至不必使用函数。如果您的实现(= 编译器)支持原子,您只需将原子说明符添加到变量声明,然后所有操作都是原子的:
_Atomic(int) toto = 65;
...
toto += 2; // is an atomic read-modify-write operation
...
if (toto == 67) // is an atomic read of toto
Atomics 有其价格(它们需要更多的计算资源),但只要您很少使用它们,它们就是同步线程的完美工具。
that it is not safe to assume that reads/writes of data in
multithreaded applications are atomic at the OS/hardware level, and
corruption of data may result
实际上 非复合 类型上的操作 int
在所有合理的体系结构上都是原子的。你看到的只是一个骗局。
(一个增量是一个复合操作:它有一个读取、一个计算和一个写入组件。每个组件都是原子的,但整个复合操作是而不是。)
但是硬件级别的原子性不是问题。您使用的高级语言根本不支持对常规类型的那种操作。您需要使用原子类型甚至有权以原子性问题相关的方式操作对象:当您可能修改另一个线程中正在使用的对象时。
(或 volatile 类型。但不要使用 volatile。使用 atomics。)
If I currently have an int assignment in a thread: messageBox[i] = 2, how do I make this assignment atomic? Equally for a reading test, like if (messageBox[i] == 2).
您几乎不需要做任何事情。在几乎所有情况下,您的线程共享(或与之通信)的数据都受到保护,无法通过互斥锁、信号量等方式进行并发访问。基本操作的实现保证了内存的同步。
这些原子的原因是为了帮助您在代码中构建更安全的竞争条件。它们有很多危险;其中:
ai += 7;
如果 ai 被适当定义,将使用原子协议。试图破译竞争条件并不能通过模糊实现来帮助。
他们也有高度依赖机器的部分。例如,上面的行在某些平台上可能会 fail [1],但是如何将失败反馈给程序?它不是 [2].
只有一个操作有处理失败的选项; atomic_compare_exchange_(弱|强)。 Weak 只尝试一次,让程序选择如何以及是否重试。强无休止地重试。仅仅尝试一次是不够的——由于中断可能会发生虚假失败——但是对非虚假失败的无休止重试也不好。
可以说,对于健壮的程序或广泛适用的库,您唯一应该使用的是 atomic_compare_exchange_weak()。
[1] 加载链接、条件存储 (ll-sc) 是在异步总线架构上进行原子事务的常用方法。 load-linked 在缓存行上设置了一个小标志,如果任何其他总线代理试图修改该缓存行,该标志将被清除。 Store-conditional 在缓存中设置小标志时存储一个值,并清除该标志;如果标志被清除,Store-conditional 会发出错误信号,因此可以尝试适当的重试操作。从这两个操作中,您可以在完全异步的总线架构上构造您喜欢的任何 atomic 操作。
ll-sc 可能对位置的缓存属性有微妙的依赖性。允许的缓存属性取决于平台,因为可以在 ll 和 sc 之间执行哪些操作。
如果您对缓存不佳的访问进行 ll-sc 操作,并盲目重试,您的程序将会锁定。这不仅仅是猜测;我不得不在基于 ARMv7 的 "safe" 系统上调试其中一个。
[2]:
#include <stdatomic.h>
int f(atomic_int *x) {
return (*x)++;
}
f:
dmb ish
.L2:
ldrex r3, [r0]
adds r2, r3, #1
strex r1, r2, [r0]
cmp r1, #0
bne .L2 /* note the retry loop */
dmb ish
mov r0, r3
bx lr
我从 SO 线程 here and here 等中了解到,假设多线程应用程序中的 reads/writes 数据在 OS/hardware 级别是原子的是不安全的,并且可能会导致数据损坏。我想知道在 Linux.
上使用带有 GCC 编译器的<stdatomic.h>
C11 库使 int
变量成为原子读写的最简单方法
如果我当前在一个线程中有一个 int
赋值:messageBox[i] = 2
,我如何使这个赋值原子化?同样适用于阅读测试,例如 if (messageBox[i] == 2)
.
对于 C11 原子,您甚至不必使用函数。如果您的实现(= 编译器)支持原子,您只需将原子说明符添加到变量声明,然后所有操作都是原子的:
_Atomic(int) toto = 65;
...
toto += 2; // is an atomic read-modify-write operation
...
if (toto == 67) // is an atomic read of toto
Atomics 有其价格(它们需要更多的计算资源),但只要您很少使用它们,它们就是同步线程的完美工具。
that it is not safe to assume that reads/writes of data in multithreaded applications are atomic at the OS/hardware level, and corruption of data may result
实际上 非复合 类型上的操作 int
在所有合理的体系结构上都是原子的。你看到的只是一个骗局。
(一个增量是一个复合操作:它有一个读取、一个计算和一个写入组件。每个组件都是原子的,但整个复合操作是而不是。)
但是硬件级别的原子性不是问题。您使用的高级语言根本不支持对常规类型的那种操作。您需要使用原子类型甚至有权以原子性问题相关的方式操作对象:当您可能修改另一个线程中正在使用的对象时。
(或 volatile 类型。但不要使用 volatile。使用 atomics。)
If I currently have an int assignment in a thread: messageBox[i] = 2, how do I make this assignment atomic? Equally for a reading test, like if (messageBox[i] == 2).
您几乎不需要做任何事情。在几乎所有情况下,您的线程共享(或与之通信)的数据都受到保护,无法通过互斥锁、信号量等方式进行并发访问。基本操作的实现保证了内存的同步。
这些原子的原因是为了帮助您在代码中构建更安全的竞争条件。它们有很多危险;其中:
ai += 7;
如果 ai 被适当定义,将使用原子协议。试图破译竞争条件并不能通过模糊实现来帮助。
他们也有高度依赖机器的部分。例如,上面的行在某些平台上可能会 fail [1],但是如何将失败反馈给程序?它不是 [2].
只有一个操作有处理失败的选项; atomic_compare_exchange_(弱|强)。 Weak 只尝试一次,让程序选择如何以及是否重试。强无休止地重试。仅仅尝试一次是不够的——由于中断可能会发生虚假失败——但是对非虚假失败的无休止重试也不好。
可以说,对于健壮的程序或广泛适用的库,您唯一应该使用的是 atomic_compare_exchange_weak()。
[1] 加载链接、条件存储 (ll-sc) 是在异步总线架构上进行原子事务的常用方法。 load-linked 在缓存行上设置了一个小标志,如果任何其他总线代理试图修改该缓存行,该标志将被清除。 Store-conditional 在缓存中设置小标志时存储一个值,并清除该标志;如果标志被清除,Store-conditional 会发出错误信号,因此可以尝试适当的重试操作。从这两个操作中,您可以在完全异步的总线架构上构造您喜欢的任何 atomic 操作。
ll-sc 可能对位置的缓存属性有微妙的依赖性。允许的缓存属性取决于平台,因为可以在 ll 和 sc 之间执行哪些操作。
如果您对缓存不佳的访问进行 ll-sc 操作,并盲目重试,您的程序将会锁定。这不仅仅是猜测;我不得不在基于 ARMv7 的 "safe" 系统上调试其中一个。
[2]:
#include <stdatomic.h>
int f(atomic_int *x) {
return (*x)++;
}
f:
dmb ish
.L2:
ldrex r3, [r0]
adds r2, r3, #1
strex r1, r2, [r0]
cmp r1, #0
bne .L2 /* note the retry loop */
dmb ish
mov r0, r3
bx lr