一般地,对于int num,num++(或++num),作为读-修改-写操作中,是不是原子.但我经常看到编译器,例如GCC,为它生成以下代码(在这里尝试):
由于第5行对应于num++一条指令,我们可以得出结论,在这种情况下num++ 是原子的吗?
如果是这样,是否意味着如此生成num++可以在并发(多线程)场景中使用而没有任何数据争用的危险(例如,我们不需要制作它,std::atomic<int>并强加相关成本,因为它是无论如何原子)?
UPDATE
请注意,这个问题不是增量是否是原子的(它不是,而且是问题的开头行).它是否可以在特定场景中,即在某些情况下是否可以利用单指令性质来避免lock前缀的开销.而且,作为公认的答案约单处理器的机器,还有部分提到这个答案,在其评论和其他人谈话解释,它可以(尽管不是C或C++).
如果有两个线程访问全局变量,那么许多教程都说使变量volatile变为阻止编译器将变量缓存在寄存器中,从而无法正确更新.但是,访问共享变量的两个线程是通过互斥锁来调用保护的东西不是吗?但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,只有那个线程可以访问变量,在这种情况下变量不需要是volatile?
那么多线程程序中volatile的用途/目的是什么?
说到C++的并发内存模型,Stroustrup的C++编程语言,第4版,第1节.41.2.1,说:
...(像大多数现代硬件一样)机器无法加载或存储任何小于单词的东西.
但是,我的x86处理器,几年前,可以存储小于一个字的对象.例如:
#include <iostream>
int main()
{
char a = 5;
char b = 25;
a = b;
std::cout << int(a) << "\n";
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果没有优化,GCC将其编译为:
[...]
movb $5, -1(%rbp) # a = 5, one byte
movb $25, -2(%rbp) # b = 25, one byte
movzbl -2(%rbp), %eax # load b, one byte, not extending the sign
movb %al, -1(%rbp) # a = b, one byte
[...]
Run Code Online (Sandbox Code Playgroud)
评论是由我提出的,但是汇编是由GCC提出的.当然,它运行良好.
显然,我不明白Stroustrup在谈到硬件可以加载和存储任何小于一个单词的内容时所说的内容.据我所知,我的计划什么也不做,但加载和存储对象小于一个字的.
C++对零成本,硬件友好的抽象的彻底关注使C++与其他易于掌握的编程语言区别开来.因此,如果Stroustrup在公交车上有一个有趣的信号心理模型,或者有其他类似的东西,那么我想了解Stroustrup的模型.
什么是 Stroustrup谈论,拜托?
更长时间的背景声明 …
8.1.2总线锁定
Intel 64和IA-32处理器提供LOCK#信号,该信号在某些关键存储器操作期间自动置位,以锁定系统总线或等效链路.当该输出信号被断言时,来自其他处理器或总线代理的用于控制总线的请求被阻止.软件可以指定在遵循LOCK语义的其他情况下将LOCK前缀添加到指令之前.
它来自英特尔手册,第3卷
听起来内存上的原子操作将直接在内存(RAM)上执行.我很困惑,因为当我分析装配输出时,我看到"没什么特别的".基本上,生成的汇编输出std::atomic<int> X; X.load()只会产生"额外"的影响.但是,它负责正确的内存排序,而不是原子性.如果我理解得X.store(2)恰到好处mov [somewhere], $2.就这样.它似乎没有"跳过"缓存.我知道将对齐(例如int)移动到内存是原子的.但是,我很困惑.
所以,我提出了疑问,但主要问题是:
注意:对于这个问题,我不是在谈论 C 或 C++语言标准。相反,我谈论的是针对特定体系结构的 gcc 编译器实现,因为语言标准对原子性的唯一保证是使用_AtomicC11 或更高版本中的类型或std::atomic<>C++11 或更高版本中的类型。另请参阅我在这个问题底部的更新。
在任何体系结构上,某些数据类型可以原子方式读取和写入,而其他数据类型则需要多个时钟周期,并且可能在操作中间被中断,如果跨线程共享该数据,则会导致损坏。
在8 位单核 AVR 微控制器(例如:Arduino Uno、Nano 或 Mini 使用的 ATmega328 mcu)上,只有8 位数据类型具有原子读取和写入(使用 gcc 编译器和gnu C 或gnu C++语言)。我在不到 2 天的时间里进行了 25 小时的马拉松式调试,然后在这里写下了这个答案。另请参阅此问题的底部以获取更多信息。以及有关使用 AVR-libc 库的 gcc 编译器编译时 AVR 8 位微控制器具有自然原子写入和自然原子读取的 8 位变量的文档。
在(32 位)STM32 单核微控制器上,任何32 位或更小的数据类型绝对自动是原子的(当使用 gcc 编译器和 gnu C 或 gnu C++ 语言编译时,因为ISO C 和 C++ 不保证这一点直到 2011 年版本,_Atomic类型为 C11 …
在ARM 文档中,它提到
Cortex-M4 处理器支持 ARMv7 未对齐访问,并将所有访问作为单个未对齐访问执行。它们通过 DCode 和系统总线接口转换为两个或多个对齐的访问。
我不清楚这是否意味着数据访问对程序员来说是原子的。然后我发现了一个 StackOverflow注释,将文档解释为:
实际上,一些 ARM 处理器(例如 Cortex-M3)支持硬件中的未对齐访问,因此即使是未对齐的读/写也是原子的。访问可能跨越多个总线周期到内存,但没有机会让另一条指令在其间跳转,因此对程序员来说是原子的。
不过,我看了看周围多一些,发现索赔违背了先前的要求:
另一个事实是,在 ARMv6 及更高版本的内核上,为了让硬件“修复”未对齐的访问,它会将其拆分为多个较小的字节负载。但是,这些不是原子的!。
那么,我相信谁呢?对于某些情况,我的项目中的打包结构中的每个元素都有 setter/getter 。换句话说,某些结构元素可能未对齐。我想知道在 Cortex-M4 上访问结构元素是否总是保证是原子的。如果不是,我想我将不得不手动启用/禁用中断或添加一些互斥锁,但如果 ARM Cortex M4 可以保证数据访问是原子的,我宁愿不这样做。