我试图理解原子和非原子操作。关于操作系统,也关于C。根据维基百科页面
考虑一个简单的计数器,不同的进程可以增加。
非原子
原始的、非原子的实现:
读取内存位置中的值;
为值加1。将新值写回内存位置。
现在,假设两个进程正在对一个共享内存位置进行增量操作:
第一个进程读取内存位置中的值;
第一个流程将值加1;
但是在它把新值写回内存位置之前,它被挂起,第二个进程被允许运行:
第二个进程读取内存位置中的值,与第一个进程读取的值相同;
第二个过程为值加1;
第二个进程将新值写入内存位置。
上述操作如何成为大气操作?我对原子操作的理解是,任何不中断地执行的事情都是原子的。例如
int b=1000;
b+=1000;
根据我的理解,应该是一个原子操作,因为这两个指令的执行都没有中断,但我从别人那里了解到,在C中没有所谓的原子操作,所以上面两个语句都是非原子的。我想了解的是,当涉及到编程语言和操作系统时,原子性有什么不同?
C99没有任何方法使变量相对于其他线程是原子的。C99没有多线程执行的概念。因此,您需要使用特定于编译器的扩展和/或cpu级指令来实现原子性。
下一个C标准,目前称为C1x,将包括原子操作。
即便如此,仅仅原子性也只能保证一个操作是原子性的,并不能保证该操作何时对其他cpu可见。为了保证可见性,在C99中需要研究CPU的内存模型,并可能使用一种特殊的CPU指令,称为fence或memory barriers。您还需要使用特定于编译器的编译器屏障告诉编译器有关它的信息。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;
所有这些都和看起来一样复杂,所以您通常应该坚持使用互斥锁,这会使事情变得更容易。
int b = 1000;
b+=1000;
在指令级被转换成多条语句。至少,准备一个寄存器或内存,赋值1000,然后获取该寄存器/内存的内容,将1000添加到内容中,并将新值(2000)重新赋值给该寄存器。如果没有锁定,操作系统可以在该操作的任何时候挂起进程/线程。此外,在多进程系统中,当您的操作正在进行时,另一个处理器可以访问该内存(在这种情况下不会是寄存器)。
当你锁定时(这就是你如何使其原子化),你在一定程度上通知操作系统不能挂起这个进程/线程,并且这个内存不应该被其他进程访问。
现在,上面的代码可能会被编译器优化为对b的内存位置赋值2000,但是为了这个答案的目的,我忽略了它。b+=1000
在我所知道的所有系统上被编译成多个指令。因此,它不是原子的。
即使b=1000
也可以是非原子的,尽管你必须努力构建一个它不是原子的情况。
事实上,C没有线程的概念,所以在C中没有什么是原子的。你需要依赖于编译器和工具的具体实现细节。
上面的语句是非原子的,因为它变成了一个move指令,将b加载到寄存器中(如果不是),然后向它添加1000,并将其存储回内存。许多指令集允许通过原子增量实现原子性,最简单的是带有锁的x86指令集。其他一些指令集使用CMPXCHG来实现相同的结果。
这个问题把我弄糊涂了。你到底是什么意思?原子性概念在程序中是相同的。语言和操作系统 关于原子性和语言,这里有一个关于JAVA原子性的链接,它可能会给您一个不同的视角:JAVA中的哪些操作被认为是原子性的?所以我想知道的是原子性是不同的吗说到编程语言操作系统?