繁体   English   中英

使用 C11 GCC 使数据读/写原子化<stdatomic.h> ?

[英]Making data reads/writes atomic in C11 GCC using <stdatomic.h>?

我从这里这里的SO 线程中了解到,假设多线程应用程序中的数据读/写在操作系统/硬件级别是原子的是不安全的,并且可能导致数据损坏。 我想知道在 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 有其价格(它们需要更多的计算资源),但只要您几乎不使用它们,它们就是同步线程的完美工具。

如果我目前在一个线程中有一个 int 赋值:messageBox[i] = 2,我如何使这个赋值原子化? 同样用于阅读测试,例如 if (messageBox[i] == 2)。

你几乎不需要做任何事情。 在几乎所有情况下,您的线程共享(或与之通信)的数据都受到保护,不会通过互斥锁、信号量等事物进行并发访问。 基本操作的实现保证了内存的同步。

这些原子的原因是帮助您在代码中构建更安全的竞争条件。 它们有许多危险; 包含:

ai += 7;

如果适当地定义了ai,将使用原子协议。 试图破译竞争条件并不能通过模糊实现来帮助。

他们还有一个高度依赖机器的部分。 例如,上面的行在某些平台上可能会失败[1],但是这种失败是如何反馈给程序的呢? 不是[2]。

只有一种操作可以选择处理失败; atomic_compare_exchange_(弱|强)。 Weak 只尝试一次,让程序选择如何以及是否重试。 强重试无休止。 仅仅尝试一次是不够的——由于中断可能会发生虚假故障——但对非虚假故障进行无休止的重试也不好。

可以说,对于健壮的程序或广泛适用的库,您应该使用的唯一位是 atomic_compare_exchange_weak()。

[1] 加载链接、条件存储 (ll-sc) 是在异步总线架构上进行原子事务的常用方法。 加载链接在缓存行上设置一个小标志,如果任何其他总线代理尝试修改该缓存行,该标志将被清除。 如果在缓存中设置了 little 标志,Store-conditional 存储一个值,并清除该标志; 如果标志被清除,Store-conditional 会发出错误信号,因此可以尝试适当的重试操作。 从这两个操作中,您可以在完全异步的总线架构上构建您喜欢的任何原子操作。

ll-sc 可能对位置的缓存属性有微妙的依赖性。 允许的缓存属性取决于平台,因为可以在 ll 和 sc 之间执行哪些操作。

如果您对缓存不佳的访问进行 ll-sc 操作,然后盲目重试,您的程序将锁定。 这不仅仅是猜测; 我不得不在基于 ARMv7 的“安全”系统上调试其中之一。

[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

假设多线程应用程序中的数据读/写在操作系统/硬件级别是原子的是不安全的,并且可能导致数据损坏

实际上,像int这样的类型的非复合操作在所有合理的架构上都是原子的。 你读到的只是一个骗局。

(增量是一个复合操作:它有一个读、一个计算和一个写组件。每个组件都是原子的,但整个复合操作不是。)

但硬件级别的原子性不是问题。 您使用的高级语言根本不支持对常规类型的这种操作。 您需要使用原子类型甚至有权以原子性问题相关的方式操作对象:当您可能修改另一个线程中正在使用的对象时。

(或 volatile 类型。但不要使用 volatile。使用原子。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM