首先,volatile
并不意味着原子访问。它专为内存映射 I/O 和信号处理等任务而设计。volatile
与使用时完全没有必要std::atomic
,除非您的平台另有说明,volatile
与线程之间的原子访问或内存排序无关。
如果您有一个在线程之间共享的全局变量,例如:
std::atomic<int> ai;
那么可见性和排序约束取决于您用于操作的内存排序参数,以及锁、线程和对其他原子变量的访问的同步效果。
在没有任何额外同步的情况下,如果一个线程向ai
那么没有什么可以保证另一个线程在任何给定时间段内都能看到该值。该标准规定它应该“在合理的时间内”可见,但任何给定的访问都可能返回陈旧的值。
默认内存顺序为std::memory_order_seq_cst
为所有人提供单一的全球总订单std::memory_order_seq_cst
跨所有变量的操作。这并不意味着您无法获得过时的值,但它确实意味着您获得的值决定了您的操作在整个顺序中的位置。
如果你有2个共享变量x
and y
,最初为零,并让一个线程将 1 写入x
另一个写 2 到y
,那么读取两者的第三个线程可能会看到 (0,0)、(1,0)、(0,2) 或 (1,2),因为操作之间没有顺序约束,因此操作可能会出现以全局顺序中的任何顺序。
如果两个写入都来自同一个线程,则x=1
before y=2
读取线程读取y
before x
那么 (0,2) 不再是一个有效的选项,因为读取y==2
意味着较早写入x
是可见的。其他 3 个配对 (0,0)、(1,0) 和 (1,2) 仍然是可能的,具体取决于 2 个读取与 2 个写入的交错方式。
如果您使用其他内存顺序,例如std::memory_order_relaxed
or std::memory_order_acquire
那么约束就会进一步放松,单一的全局排序不再适用。如果没有额外的同步,线程甚至不必就两个存储的顺序达成一致以分隔变量。
保证您拥有“最新”值的唯一方法是使用读取-修改-写入操作,例如exchange()
, compare_exchange_strong()
or fetch_add()
。读取-修改-写入操作有一个额外的约束,即它们始终对“最新”值进行操作,因此一系列ai.fetch_add(1)
一系列线程的操作将返回一系列没有重复或间隙的值。在没有附加约束的情况下,仍然无法保证哪些线程会看到哪些值。特别要注意的是,使用 RMW 操作不会强制其他线程的更改更快地变得可见,这只是意味着如果更改是not如果 RMW 看到,那么所有线程必须同意它们在该原子变量的修改顺序中比 RMW 操作晚。来自不同线程的存储仍然可以延迟任意时间,具体取决于 CPU 何时actually将存储发布到内存(而不仅仅是其自己的存储缓冲区),身体上的执行线程的 CPU 之间的距离有多远(在多处理器系统的情况下),以及缓存一致性协议的详细信息。
使用原子操作是一个复杂的主题。我建议您阅读大量背景材料,并在使用原子编写生产代码之前检查已发布的代码。在大多数情况下,编写使用锁的代码会更容易,而且效率也不会明显降低。