关于多线程:C ++是否读写int原子?

关于多线程:C ++是否读写int原子?

Are C++ Reads and Writes of an int Atomic?

我有两个线程,一个线程更新一个int,另一个读取它。 这是一个统计值,其中读取和写入的顺序无关紧要。

我的问题是,是否仍然需要同步对此多字节值的访问? 或者,换一种说法,可以完成部分写入并使其中断,然后进行读取。

例如,考虑一个值为0x0000FFFF的值,该值的增量值为0x00010000。

有一段时间我应该担心该值看起来像0x0001FFFF吗? 当然,类型越大,越可能发生这种情况。

我一直同步这些类型的访问,但很好奇社区的想法。


男孩,什么问题。答案是:

Yes, no, hmmm, well, it depends

一切都取决于系统的体系结构。在IA32上,正确对齐的地址将是原子操作。未对齐的写入可能是原子的,这取决于所使用的缓存系统。如果内存位于单个L1高速缓存行中,则它是原子的,否则就不是原子的。 CPU和RAM之间的总线宽度会影响原子性质:在8086上正确对齐的16位写入是原子的,而在8088上进行相同的写入则不是,因为8088仅具有8位总线,而8086却具有8位总线。 16位总线。

另外,如果您使用的是C / C ++,请不要忘记将共享值标记为volatile,否则优化程序将认为该变量永远不会在您的线程之一中更新。


最初,人们可能会认为对本机机器大小的读取和写入是原子的,但是要处理许多问题,包括处理器/内核之间的缓存一致性。在Windows上使用原子操作,例如Interlocked *,在Linux上使用等效操作。 C ++ 0x将具有一个"原子"模板,以将它们包装在一个不错的跨平台界面中。现在,如果您正在使用平台抽象层,它可能会提供这些功能。 ACE可以,请参见类模板ACE_Atomic_Op。


如果您正在读取/写入4字节的值,并且它在内存中是DWORD对齐的,并且您正在I32体系结构上运行,则读写是原子的。


是的,您需要同步访问。在C ++ 0x中,它将是一场数据竞赛,并且是未定义的行为。使用POSIX线程,它已经是不确定的行为。

实际上,如果数据类型大于本机字大小,则可能会得到错误的值。另外,由于优化会移动读取和/或写入,另一个线程可能永远看不到写入的值。


在Windows上,Interlocked *** Exchange *** Add被保证是原子的。


您必须同步,但是在某些体系结构上,有进行同步的有效方法。

最好是使用子例程(可能在宏后面隐藏),以便您可以有条件地将实现替换为特定于平台的实现。

Linux内核已经有一些这样的代码。


为了回应大家在楼上所说的话,C ++ 0x之前的语言无法保证有关从多个线程访问共享内存的任何信息。任何保证都取决于编译器。


绝对不行!
来自我们最高的C ++权威M.Boost的回答:
不保证对"普通"变量的操作是原子的。


有人认为++ c是原子的,但是关注生成的程序集。例如,'gcc -S':

1
2
3
movl    cpt.1586(%rip), %eax
addl    $1, %eax
movl    %eax, cpt.1586(%rip)

要增加一个int,编译器首先将其加载到寄存器中,然后将其存储回内存中。这不是原子的。


tc
我认为,当您使用常量(如6)时,指令将不会在一个机器周期内完成。
尝试查看与x ++相比x + = 6的指令集


让我们举个例子

1
2
3
int x;
x++;
x=x+5;

假定第一条语句是原子的,因为它会转换为占用一个CPU周期的单个INC汇编指令。但是,第二个分配需要几个操作,因此显然不是原子操作。

另一个例如

1
x=5;

同样,您必须分解代码以查看此处实际发生的情况。


除了上面提到的缓存问题...

如果将代码移植到具有较小寄存器大小的处理器,它将不再是原子的。

IMO,线程问题太棘手,无法冒险。


我同意很多人,尤其是杰森。在Windows上,可能会使用InterlockedAdd及其朋友。


不,它们不是(或者至少您不能认为它们是)。话虽如此,有一些技巧可以自动完成,但是它们通常是不可移植的(请参阅"比较并交换")。


唯一可移植的方法是为编译器使用signal.h标头中定义的sig_atomic_t类型。在大多数C和C ++实现中,这是一个整数。然后将变量声明为" volatile sig_atomic_t"。


推荐阅读