原子读取是否保证读取最新值?
Does atomic read guarantees reading of latest value?
在 C++ 中,我们有关键字 volatile
和 atomic
class。它们的区别在于volatile不保证线程安全的并发读写,只是保证编译器不会将变量的值存入缓存,而是从内存中加载变量,而atomic保证线程安全的并发读写。
正如我们所知,原子读取操作不可分割,即当一个或多个线程读取变量的值时,两个线程都不能向变量写入新值,所以我认为我们总是读取最新值,但我不确定:)
所以,我的问题是:如果我们声明原子变量,我们是否总是获得变量调用 load()
操作的最新值?
当我们谈论现代架构上的内存访问时,我们通常会忽略 "exact location" 从中读取值。
读取操作可以从缓存 (L0/L1/...)、RAM 甚至硬盘驱动器中获取数据(例如,当内存被交换时)。
这些关键字告诉编译器在访问数据时使用哪些汇编操作。
易变
一个关键字,告诉编译器总是从内存中读取变量的值,而不是从寄存器中读取。
这个"memory"仍然可以是缓存,但是,如果缓存中的这个"address"被认为是"dirty",意味着这个值已经被不同的处理器改变了,该值将被重新加载。
这确保我们永远不会读取过时的值。
但是,如果类型 declare volatile
不是原语,其 read/write 操作本质上是原子的(关于 read/write 它的汇编指令),我们可能读取一个中间值(在 reader 读取它时,作者设法只写入了一半的字节)。
原子
并且编译器看到一个 load
(读)操作,它基本上做了与 volatile
值完全相同的事情,除了使用原子操作(这意味着我们永远不会读取中间值)。
那么,有什么区别呢???
区别在于交叉CPU写操作。
使用 volatile 变量时,如果 CPU 1 设置值,而 CPU 2 读取它,则 reader 可能读取旧值。
但是,怎么可能呢? volatile 关键字保证我们不会读取过时的值!
嗯,那是因为作者没有发表价值!尽管 reader 尝试读取它,但它读取的是旧版本。
当编译器偶然发现对原子变量的 store
(写入)操作时:
- 在内存中自动设置值
- 宣布值已更改
公告后,所有CPU都会知道他们应该重新读取变量的值,因为他们的缓存将被标记为"dirty"。
此机制与对文件执行的操作非常相似。当您的应用程序写入硬盘驱动器上的文件时,其他应用程序可能会或可能不会看到新信息,具体取决于您的应用程序是否将数据刷新到硬盘驱动器。
如果数据没有被刷新,那么它只是驻留在您的应用程序缓存中的某个地方,并且只有它自己可见。刷新后,打开文件的任何人都会看到新状态。
if we declare atomic variable, do we always get the latest value of
the variable calling load() operation?
是的,对于 latest.
的某些定义
并发的问题是不可能以通常的方式争论事件的顺序。这是由于硬件的一个基本限制,在该硬件中建立跨多个内核的全局操作顺序的唯一方法是将它们序列化(并消除该过程中并行计算的所有性能优势)。
现代处理器提供的是一种选择加入机制,用于在某些操作之间重新建立顺序。原子是该机制的语言级抽象。想象一个场景,其中两个 atomic<int>
s a
和 b
在线程之间共享(并且让我们进一步假设它们被初始化为 0
):
// thread #1
a.store(1);
b.store(1);
// thread #2
while(b.load() == 0) { /* spin */ }
assert(a.load() == 1);
这里的断言保证成立。线程 #2 将观察 a
的 "latest" 值。
标准没有提到的是循环将观察到 b
的值从 0
变为 1
的确切时间。我们知道它会在线程 #1 写入之后的某个时间发生,我们也知道它会在写入 a
之后发生。但是不知道过了多久。
这种推理因以下事实而变得更加复杂,即在发生某些写入时允许不同的线程不一致。如果切换到 weaker memory ordering,一个线程可能会观察到以 不同的 顺序写入不同的原子变量,这与另一个线程观察到的顺序不同。
在 C++ 中,我们有关键字 volatile
和 atomic
class。它们的区别在于volatile不保证线程安全的并发读写,只是保证编译器不会将变量的值存入缓存,而是从内存中加载变量,而atomic保证线程安全的并发读写。
正如我们所知,原子读取操作不可分割,即当一个或多个线程读取变量的值时,两个线程都不能向变量写入新值,所以我认为我们总是读取最新值,但我不确定:)
所以,我的问题是:如果我们声明原子变量,我们是否总是获得变量调用 load()
操作的最新值?
当我们谈论现代架构上的内存访问时,我们通常会忽略 "exact location" 从中读取值。
读取操作可以从缓存 (L0/L1/...)、RAM 甚至硬盘驱动器中获取数据(例如,当内存被交换时)。
这些关键字告诉编译器在访问数据时使用哪些汇编操作。
易变
一个关键字,告诉编译器总是从内存中读取变量的值,而不是从寄存器中读取。
这个"memory"仍然可以是缓存,但是,如果缓存中的这个"address"被认为是"dirty",意味着这个值已经被不同的处理器改变了,该值将被重新加载。
这确保我们永远不会读取过时的值。
但是,如果类型 declare volatile
不是原语,其 read/write 操作本质上是原子的(关于 read/write 它的汇编指令),我们可能读取一个中间值(在 reader 读取它时,作者设法只写入了一半的字节)。
原子
并且编译器看到一个 load
(读)操作,它基本上做了与 volatile
值完全相同的事情,除了使用原子操作(这意味着我们永远不会读取中间值)。
那么,有什么区别呢???
区别在于交叉CPU写操作。 使用 volatile 变量时,如果 CPU 1 设置值,而 CPU 2 读取它,则 reader 可能读取旧值。
但是,怎么可能呢? volatile 关键字保证我们不会读取过时的值!
嗯,那是因为作者没有发表价值!尽管 reader 尝试读取它,但它读取的是旧版本。
当编译器偶然发现对原子变量的 store
(写入)操作时:
- 在内存中自动设置值
- 宣布值已更改
公告后,所有CPU都会知道他们应该重新读取变量的值,因为他们的缓存将被标记为"dirty"。
此机制与对文件执行的操作非常相似。当您的应用程序写入硬盘驱动器上的文件时,其他应用程序可能会或可能不会看到新信息,具体取决于您的应用程序是否将数据刷新到硬盘驱动器。
如果数据没有被刷新,那么它只是驻留在您的应用程序缓存中的某个地方,并且只有它自己可见。刷新后,打开文件的任何人都会看到新状态。
if we declare atomic variable, do we always get the latest value of the variable calling load() operation?
是的,对于 latest.
的某些定义并发的问题是不可能以通常的方式争论事件的顺序。这是由于硬件的一个基本限制,在该硬件中建立跨多个内核的全局操作顺序的唯一方法是将它们序列化(并消除该过程中并行计算的所有性能优势)。
现代处理器提供的是一种选择加入机制,用于在某些操作之间重新建立顺序。原子是该机制的语言级抽象。想象一个场景,其中两个 atomic<int>
s a
和 b
在线程之间共享(并且让我们进一步假设它们被初始化为 0
):
// thread #1
a.store(1);
b.store(1);
// thread #2
while(b.load() == 0) { /* spin */ }
assert(a.load() == 1);
这里的断言保证成立。线程 #2 将观察 a
的 "latest" 值。
标准没有提到的是循环将观察到 b
的值从 0
变为 1
的确切时间。我们知道它会在线程 #1 写入之后的某个时间发生,我们也知道它会在写入 a
之后发生。但是不知道过了多久。
这种推理因以下事实而变得更加复杂,即在发生某些写入时允许不同的线程不一致。如果切换到 weaker memory ordering,一个线程可能会观察到以 不同的 顺序写入不同的原子变量,这与另一个线程观察到的顺序不同。