volatile是一种在C / C ++中使单个字节成为原子的正确方法吗?

时间:2011-02-08 17:34:10

标签: c++ c atomic

我知道volatile不会在int上强制执行原子性,但是如果你访问单个字节会这样做吗?如果我没记错的话,语义要求写入和读取始终来自内存。

或换句话说:CPU是否始终以原子方式读写字节?

5 个答案:

答案 0 :(得分:21)

标准不仅没有说明关于原子性的任何内容,而且你甚至可能会提出错误的问题。

CPU通常以原子方式读写单个字节。问题出现的原因是当您有多个内核时,并非所有内核都会将该字节视为同时写入。事实上,在所有核心看到写入之前,可能需要相当长的时间(在CPU中,数千或数百万条指令(也就是微秒或毫秒))。

所以,你需要一些有点错误的C ++ 0x原子操作。他们使用CPU指令确保事物的顺序不会搞砸,并且当其他内核查看您编写后写入的值时,他们会看到新值,而不是旧值。他们的工作不仅仅是操作的原子性,而是确保适当的同步步骤也会发生。

答案 1 :(得分:5)

标准对原子性一无所知。

答案 2 :(得分:4)

volatile关键字用于表示变量(intchar或其他)可以从外部不可预测的来源获得值。这通常会阻止编译器优化变量。

对于 atomic ,您需要检查编译器的文档以查看它是否提供任何帮助或声明或pragma。

答案 3 :(得分:4)

在任何理智的cpu上,读取和写入任何对齐的,字大小或更小的类型都是原子的。这不是问题。问题是:

  • 仅仅因为读取和写入是原子的,所以读取/修改/写入序列不是原子的。在C语言中,x++在概念上是读/修改/写周期。您无法控制编译器是否生成原子增量,而且通常不会。
  • 缓存同步问题。在中间废话架构(几乎所有非x86),硬件太愚蠢,以确保每个CPU看到的内存视图反映了写入发生的顺序。例如,如果cpu 0写入地址A然后写入B,则cpu 1可能会在地址B处看到更新,但不会在地址A处看到更新。您需要特殊的内存fence / barrier操作码来解决此问题,并且编译器不会生成他们适合你。

第二点仅在SMP /多核系统上很重要,所以如果你很高兴将自己限制在单核,你可以忽略它,然后普通读写将在C中是原子的在任何理智的CPU架构上。但是你不能用它来做很多有用的事情。 (例如,以这种方式实现简单锁定的唯一方法是O(n)空间,其中n是线程数。)

答案 4 :(得分:3)

简短回答:不要使用volatile来保证原子性。

答案很长 有人可能会认为,由于CPU在单个指令中处理字,因此简单的字操作本质上是线程安全的。使用volatile的想法是确保编译器不对共享变量中包含的值做出假设。

在现代多处理器机器上,这种假设是错误的。鉴于不同的处理器内核通常都有自己的缓存,可能会出现重新排序对主内存的读写操作并且代码最终无法按预期运行的情况。

出于这个原因,当线程之间共享访问内存时,总是使用锁,例如互斥锁或关键部分。当没有争用时(通常不需要进行系统调用),它们会出乎意料地便宜,并且它们会做正确的事情。

通常,它们会通过调用数据存储器屏障(ARM上的DMB)指令来防止乱序读取和写入,这些指令可确保读取和写入以正确的顺序发生。查看here了解更多详情。

volatile的另一个问题是它会阻止编译器进行优化,即使完全可以这样做。