原子读取是否可以保证读取最新值?

时间:2018-10-28 14:04:13

标签: c++ multithreading atomic volatile

在C ++中,我们有关键字volatileatomic类。它们之间的区别在于volatile不能保证线程安全的并发读写,而只是确保编译器不会在高速缓存中存储变量的值,而是从内存中加载变量,而atomic保证线程安全的并发读写。

我们知道,原子读取操作不可分割,即,当一个或多个线程读取变量值时,两个线程都无法将新值写入变量,因此我认为我们总是读取最新值,但是我不确定:)

所以,我的问题是:如果我们声明原子变量,我们是否总是获得调用load()操作的变量的最新值

2 个答案:

答案 0 :(得分:0)

当我们谈论现代体系结构上的内存访问时,我们通常会忽略从中读取值的“确切位置”。

读取操作可以从缓存(L0 / L1 / ...),RAM甚至硬盘驱动器(例如,交换内存)中获取数据。

这些关键字告诉编译器访问数据时要使用哪些汇编操作。

易失性

一个关键字,告诉编译器始终从内存而不是从寄存器读取变量的值。

此“内存”仍然可以是高速缓存,但是,如果高速缓存中的“地址”被认为是“脏”的,这意味着该值已由其他处理器更改,则将重新加载该值。

这可以确保我们永远不会读取过时的值。

但是,如果类型声明volatile不是原始类型,则其读/写操作本质上是原子的(就读/写它的汇编指令而言),我们可能会读取一个中间值(写者设法在读者阅读时只写了一半的字节。

原子

并且编译器看到load(读)操作,除了使用原子操作外,它基本上完成了与volatile值完全相同的操作(这意味着我们永远不会读取一个中间值)。

那么,有什么区别?

区别在于跨CPU写操作。 使用volatile变量时,如果CPU 1设置了该值,而CPU 2读取了该值,则读取器可能会读取旧的值。

但是,怎么可能呢? volatile关键字承诺我们不会读取过时的值!

好吧,那是因为作者没有发布价值!尽管读者试图阅读它,但它会阅读旧的。

当编译器偶然遇到原子变量的store(写)操作时,它会:

  • 自动在内存中设置值
  • 宣布值已更改

宣布之后,所有CPU都知道它们应该重新读取变量的值,因为其缓存将被标记为“脏”。

此机制与对文件执行的操作非常相似。当您的应用程序写入硬盘驱动器上的文件时,其他应用程序可能会或可能不会看到新信息,具体取决于您的应用程序是否将数据刷新到硬盘驱动器上。

如果未刷新数据,则它仅驻留在应用程序缓存中的某个位置,并且仅可见。刷新文件后,打开文件的任何人都将看到新状态。

答案 1 :(得分:0)

  

如果我们声明原子变量,是否总是得到的最新值   调用load()操作的变量?

是的,对于最新的某些定义。

并发性的问题是不可能以通常的方式争论事件的顺序。这是由于硬件的一个基本限制,即跨多个内核建立全局操作顺序的唯一方法是将它们串行化(并消除过程中并行计算的所有性能优势)。

现代处理器提供的是一种选择加入机制,可以在某些操作之间重新建立顺序。原子是该机制的语言级抽象。设想一个场景,其中两个atomic<int>ab在线程之间共享(让我们进一步假设它们已初始化为0):

// thread #1
a.store(1);
b.store(1);

// thread #2
while(b.load() == 0) { /* spin */ }
assert(a.load() == 1);

这里的断言肯定会成立。线程#2将观察a的“最新”值。

该标准没有讨论的是什么时候确切的循环会观察到b的值从0变为1。我们知道它会在线程#1写入之后的某个时间发生,并且我们也知道它将在写入a之后发生。但是我们不知道会持续多久。

这种推理由于以下事实而变得更加复杂:在进行某些写操作时,允许不同的线程不同意。如果切换到weaker memory ordering,则一个线程可能会观察到对不同原子变量的写操作,而这些原子变量却以与另一个线程所观察到的不同的不同顺序发生。