C ++是否读取和写入了原子?

时间:2008-09-10 14:26:41

标签: c++ multithreading synchronization

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

我的问题是,我是否需要同步访问此多字节值?或者换句话说,写入的一部分可以完成并被中断,然后读取就会发生。

例如,假设值= 0x0000FFFF,其值增加为0x00010000。

我应该担心这个值看起来像0x0001FFFF吗?当然,类型越大,就越有可能发生这样的事情。

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

16 个答案:

答案 0 :(得分:64)

男孩,真是个问题。答案是:

  

是的,不,嗯,这取决于

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

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

答案 1 :(得分:44)

起初人们可能认为本机大小的读取和写入是原子的,但是有许多问题要处理,包括处理器/核心之间的缓存一致性。在Windows上使用Interlocked *等原子操作,在Linux上使用等效操作。 C ++ 0x将有一个“原子”模板将它们包装在一个漂亮的跨平台接口中。目前,如果您使用的是平台抽象层,它可能会提供这些功能。 ACE确实如此,请参阅课程模板ACE_Atomic_Op

答案 2 :(得分:11)

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

答案 3 :(得分:8)

是的,您需要同步访问。在C ++ 0x中,它将是一个数据争用和未定义的行为。对于POSIX线程,它已经是未定义的行为。

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

答案 4 :(得分:3)

您必须同步,但在某些架构上,有一些有效的方法可以实现。

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

Linux内核已经有了一些代码。

答案 5 :(得分:3)

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

答案 6 :(得分:1)

为了回应每个人在楼上说的话,C ++ 0x之前的语言无法保证来自多个线程的共享内存访问。任何保证都取决于编译器。

答案 7 :(得分:1)

肯定不会! 来自我们最高C ++权威的答案,M。Boost:
Operations on "ordinary" variables are not guaranteed to be atomic.

答案 8 :(得分:0)

不,他们不是(或者至少你不能认为他们是)。话虽如此,有些技巧可以原子地执行此操作,但它们通常不可移植(请参阅Compare-and-swap)。

答案 9 :(得分:0)

我同意很多人,特别是Jason。在Windows上,人们可能会使用InterlockedAdd及其朋友。

答案 10 :(得分:0)

从上面提到的缓存问题中解释出来......

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

IMO,线程问题太棘手了,无法承担风险。

答案 11 :(得分:0)

让我们举个例子

int x;
x++;
x=x+5;

假设第一个语句是原子语句,因为它转换为单个INC程序集指令,它占用一个CPU周期。但是,第二个赋值需要几个操作,所以它显然不是原子操作。

另一个例如,

x=5;

同样,你必须反汇编代码才能看到这里到底发生了什么。

答案 12 :(得分:0)

TC, 我认为当你使用常量(如6)时,指令不会在一个机器周期内完成。 与x ++

相比,尝试查看x + = 6的指令集

答案 13 :(得分:0)

有些人认为++ c是原子的,但是要关注生成的汇编。例如,使用'gcc -S':

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

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

答案 14 :(得分:0)

读取和写入是原子的,但您还需要担心编译器重新排序您的代码。编译器优化可能会违反代码中语句的先发生关系。通过使用 atomic,您不必担心。 ... 原子 i;

soap_status = GOT_RESPONSE ; 我 = 1

在上面的例子中,变量'i'只会在我们得到soap响应后设置为1。

答案 15 :(得分:-1)

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