35

在 C 中,是i+=1;原子的吗?

4

12 回答 12

93

C标准没有定义它是否是原子的。

在实践中,如果给定操作是原子的,您永远不会编写失败的代码,但如果不是原子操作,您可能会编写失败的代码。所以假设不是。

于 2009-11-24T13:58:30.653 回答
20

不。

C 语言标准保证的唯一操作是原子操作是向/从sig_atomic_t定义在<signal.h>.

(C99,第 7.14 章信号处理。)

于 2009-11-24T13:58:42.277 回答
13

在 C 中定义,没有。在实践中,也许。把它写成汇编。

该标准不做任何保证。

因此,可移植程序不会做出假设。目前尚不清楚您的意思是“必须是原子的”,还是“在我的 C 代码中恰好是原子的”,第二个问题的答案是它取决于很多事情:

  • 并非所有机器都具有增量内存操作。有些需要加载和存储值才能对其进行操作,所以答案是“从不”。

  • 在确实具有增量内存操作的机器上,无法保证编译器不会输出加载、增量和存储序列,或者使用其他一些非原子指令。

  • 在确实具有增量内存操作的机器上,它相对于其他 CPU 单元可能是原子的,也可能不是原子的。

  • 在确实具有原子增量内存操作的机器上,它可能不会被指定为架构的一部分,而只是特定版本的 CPU 芯片的属性,甚至只是某些核心逻辑或主板设计的属性。

至于“我如何以原子方式做到这一点”,通常有一种方法可以快速做到这一点,而不是诉诸(更昂贵的)协商互斥。有时这涉及特殊的冲突检测可重复代码序列。最好在汇编语言模块中实现这些,因为它是特定于目标的,因此对 HLL 没有可移植性优势。

最后,因为不需要(昂贵的)协商互斥的原子操作速度很快,因此很有用,并且在任何情况下都需要可移植代码,系统通常有一个库,通常用汇编编写,已经实现了类似的功能。

于 2009-11-25T00:37:19.010 回答
4

表达式是否是原子的仅取决于编译器生成的机器代码以及它将运行的 CPU 架构。除非可以在一条机器指令中实现加法,否则它不太可能是原子的。

如果您使用的是 Windows,那么您可以使用InterlockedIncrement() API 函数来保证原子增量。减量等也有类似的功能。

于 2009-11-24T14:03:59.817 回答
3

尽管 i 对于 C 语言可能不是原子的,但应该注意的是,它在大多数平台上都是原子的。GNU C 库文档指出:

在实践中,您可以假设 int 和其他不超过 int 的整数类型是原子的。你也可以假设指针类型是原子的;这很方便。这两个假设在 GNU C 库支持的所有机器和我们知道的所有 POSIX 系统上都是正确的。

于 2009-11-24T16:31:02.453 回答
2

这实际上取决于您的目标和您的 uC/处理器的助记符集。如果 i 是一个保存在寄存器中的变量,那么它可以是原子的。

于 2009-11-24T13:58:18.127 回答
1

不,不是。如果 i 的值尚未加载到寄存器之一,则无法在一条汇编指令中完成。

于 2009-11-24T13:55:58.507 回答
1

通常不会。

如果ivolatile,那么它将取决于您的 CPU 架构和编译器 - 如果在主内存中添加两个整数在您的 CPU 上是原子的,那么该 C 语句可能是带有volatile int i.

于 2009-11-24T13:56:59.130 回答
1

C / C++ 语言本身并没有声称原子性或缺乏原子性。您需要依赖内在函数或库函数来确保原子行为。

于 2009-11-24T13:59:22.073 回答
1

只需在其周围放置一个互斥锁或信号量即可。当然它不是原子的,你可以用 50 个左右的线程来创建一个测试程序,访问同一个变量并递增它,自己检查它

于 2009-11-24T14:03:54.997 回答
1

不,C 标准不保证原子性,实际上,操作不会是原子的。您必须使用库(例如Windows API)或编译器内置函数(GCCMSVC)。

于 2009-11-24T19:21:07.617 回答
1

您的问题的答案取决于i是局部static变量还是全局变量。如果i是一个static或全局变量,则不,该语句i += 1不是原子的。但是,如果i它是一个局部变量,那么该语句对于在 x86 架构上运行的现代操作系统来说是原子的,也可能是其他架构。@Dan Cristoloveanu 在局部变量案例中处于正确的轨道上,但还有更多可以说的。

(在下文中,我假设一个现代操作系统在 x86 架构上具有保护,线程完全通过任务切换实现。)

鉴于这是 C 代码,语法i += 1意味着它i是某种整数变量,如果它是局部变量,则其值存储在诸如寄存器之类的寄存器中%eax或存储在堆栈中。首先处理简单的情况,如果 的值i存储在寄存器中,比如说%eax,那么 C 编译器很可能会将语句翻译成如下内容:

addl    $1, %eax

这当然是原子的,因为没有其他进程/线程应该能够修改正在运行的线程的寄存器,并且线程本身在该指令完成之前%eax不能再次修改。%eax

如果 的值i存储在堆栈中,则这意味着存在内存获取、增量和提交。就像是:

movl    -16(%esp), %eax
addl    $1, %eax
movl    %eax, -16(%esp)  # this is the commit. It may actually come later if `i += 1` is part of a series of calculations involving `i`.

通常这一系列操作不是原子的。但是,在现代操作系统上,进程/线程不应该能够修改另一个线程的堆栈,因此这些操作没有其他进程能够干扰的情况下完成。因此,在这种情况下,该语句i += 1也是原子的。

于 2010-05-30T15:05:04.947 回答