如何将非原子操作转换为原子操作?

5
我正在尝试理解原子操作和非原子操作。涉及操作系统和C语言方面的内容。根据维基百科页面这里所述。
考虑一个简单的计数器,多个进程可以对其进行递增。
非原子操作 朴素的非原子实现:
读取内存位置中的值;
将值加一;
将新值写回内存位置。
现在,想象两个进程正在运行并递增单个共享内存位置:
第一个进程读取内存位置中的值;
第一个进程将值加一;
但在它写入新值回到内存位置之前被挂起,第二个进程被允许运行:
第二个进程读取内存位置中的值,与第一个进程读取的相同;
第二个进程将值加一;
第二个进程将新值写入内存位置。
如何将上述操作变为原子操作?我理解的原子操作是指能够无中断地执行的任何操作。例如:
int b=1000;
  b+=1000;

根据我的理解,应该是原子操作,因为两个指令都没有中断地执行,但我从某人那里学到,在C语言中没有所谓的原子操作,因此上述两个语句都不是原子操作。那么我想要了解的是,当涉及编程语言时,原子性与操作系统有何不同?


汇编语言是这里的答案,但那是通往黑暗面的道路。 - Ulterior
@David Heffernan 如果你想要了解并控制执行过程,你必须使用汇编语言,否则就得听从编译器的摆布。 - Ulterior
@user 大多数人实际上使用库。 - David Heffernan
@David Heffernan,这里给你一个-1,感谢你的深刻见解。 - Ulterior
@user:我猜你不是有意要给这个问题点踩,所以我点了一个赞来抵消它。 - Sander De Dycker
5个回答

4
C99没有任何方法使变量在其他线程中具有原子性。C99没有多线程执行的概念。因此,您需要使用编译器特定的扩展和/或CPU级别的指令来实现原子性。
下一个C标准,目前称为C1x,将包括原子操作。
即使如此,仅仅原子性保证一个操作是原子的,并不保证该操作何时对其他CPU可见。要实现可见性保证,在C99中,您需要研究CPU的内存模型,并可能使用一种名为“fences”或“memory barriers”的特殊类型的CPU指令。您还需要告诉编译器,使用一些特定于编译器的编译器屏障。C1x定义了几个内存排序方式,当您使用原子操作时,可以决定使用哪个内存排序方式。
一些例子:
/* NOT atomic */
b += 1000;

/* GCC-extension, only in newish GCCs 
 *   requirements on b's loads are CPU-specific
 */
__sync_add_and_fetch(&b, 1000);

/* GCC-extension + x86-assembly, 
 *   b should be aligned to its size (natural alignment), 
 *   or loads will not be atomic
 */
__asm__ __volatile__("lock add $1000, %0" : "+r"(b));


/* C1x */
#include <stdatomic.h>
atomic_int b = ATOMIC_INIT(1000);
int r = atomic_fetch_add(&b, 1000) + 1000;

这些都很复杂,所以通常应该坚持使用互斥锁,这样可以使事情变得更容易。


3
int b = 1000;
b+=1000;

在指令级别上,这段代码将被转换为多个语句。至少需要准备一个寄存器或内存,分配1000,然后获取该寄存器/内存的内容,将1000添加到内容中,并重新分配新值(2000)给该寄存器。如果没有锁定,操作系统可以在该操作的任何时候挂起进程/线程。此外,在多处理器系统上,不同的处理器可能会在您的操作进行时访问该内存(在这种情况下不会是寄存器)。

当您取出锁定(这是使此操作原子化的方法)时,部分地通知操作系统不要挂起此进程/线程,并且其他进程不应访问此内存。

现在,编译器可能会将上述代码优化为将2000简单分配给b的内存位置,但出于本答案的目的,我忽略了这一点。


3

b+=1000 在我所知道的所有系统上都会被编译成多条指令,因此它不是原子性的。

即使是 b=1000 也可能不是原子性的,虽然你需要花费一些功夫才能构造出不是原子性的情况。

实际上,C语言没有线程的概念,因此在C中没有任何东西是原子性的。你需要依赖于编译器和工具的具体实现细节。


所以您的意思是,用于访问存储变量的特定内存区域的汇编指令将是原子性的。 - Registered User
那么 int b; 或者 b=1000; 中的任何一个是原子操作吗? - Registered User
int b不会生成指令,所以问题毫无意义。在C中没有任何保证是原子的。 - David Heffernan
@注册用户:b = 1000 是否是原子操作取决于 CPU,可能还取决于 b 的对齐和大小。即使如此,您可能没有(或者有非常弱的)可见性保证。 - ninjalj

0
上述语句是非原子性的,因为它变成了一个移动指令,将b加载到寄存器中(如果没有),然后加上1000并存回内存。许多指令集通过原子增量来实现原子性,最简单的是x86,使用lock addl dest,src;一些其他指令集使用cmpxchg来实现相同的结果。

0
我想要了解的是,编程语言中的原子性与操作系统有何不同?
这个问题让我有些困惑。您具体指的是什么?在编程语言和操作系统中,原子性概念是相同的。
关于原子性和编程语言,例如JAVA中的原子操作,这里有一个链接What operations in Java are considered atomic?,或许可以给您一个不同的视角。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接