我一直试图谷歌我的问题,但老实说,我不知道如何简洁地陈述问题.
假设我在多核Intel系统中有两个线程.这些线程在同一个NUMA节点上运行.假设线程1写入X一次,然后只是偶尔读取它向前移动.进一步假设,线程2连续读取X. 如果我不使用内存栅栏,在线程1写入X和线程2看到更新值之间可以有多长时间?
我知道X的写入将转到存储缓冲区并从那里到缓存,此时MESIF将启动,线程2将通过QPI查看更新的值.(或者至少这是我收集到的).我假设存储缓冲区将被写入存储围栏中的缓存或者是否需要重用该存储缓冲区条目,但我不知道存储缓冲区是否已分配给写入.
最终我要为自己回答的问题是,如果线程2有可能在一个相当复杂的应用程序中看到线程1的写入几秒钟而正在做其他工作.
在 C++ 中,我们有关键字volatile和atomic类。它们之间的区别是,volatile 不保证线程安全的并发读写,而只是保证编译器不会将变量的值存储在缓存中,而是从内存中加载变量,而 atomic 保证线程安全的并发读写。
众所周知,原子读操作是不可分割的,即当一个或多个线程读取变量的值时,两个线程都不能将新值写入变量,所以我认为我们总是读取最新值,但我不确定:)
所以,我的问题是:如果我们声明原子变量,我们是否总是获得变量调用load()操作的最新值?
我记得我在C语言中遇到过某些称为原子类型的类型,但我们从未研究过它们。
那么,如何做,他们不同于普通类型的喜欢int,float,double,long等等,和他们有什么用途?
关于std::atomic,C++11 标准规定,存储到原子变量将在“合理的时间内”对该变量的加载变得可见。
从 29.3p13 开始:
\n\n\n\n\n实现应该使原子存储在合理的时间内对原子加载可见。
\n
然而,我很想知道在处理基于 MESI 缓存一致性协议(x86、x86-64、ARM 等)的特定 CPU 架构时实际会发生什么。
\n\n如果我对 MESI 协议的理解是正确的,那么一个核心总是会立即读取先前写入/正在由另一个核心写入的值,可能是通过窥探它。(因为写入值意味着发出 RFO 请求,这反过来会使其他缓存行无效)
\n\n这是否意味着当一个线程 A 将一个值存储到 an 中时std::atomic,另一个连续对该原子进行加载的线程 B 实际上总是会观察到 A 在 MESI 架构上写入的新值?(假设没有其他线程正在对该原子执行操作)
\xe2\x80\x9csuccessively\xe2\x80\x9d 我的意思是在线程 A 发出原子存储之后。(修改顺序已更新)
\n我知道volatile在文件范围定义的变量.不允许编译器对这些变量进行假设.它们几乎可以随时更改,编译时不能优化变量的读取.
现在我找到了这段代码
BOOL InstallHandler()
{
volatile BOOL b_bulk_erase = FALSE;
volatile BOOL b_test_read_write = FALSE;
volatile BOOL b_continue = TRUE;
...
if (b_test_read_write)
{
read();
write();
}
}
Run Code Online (Sandbox Code Playgroud)
如何volatile对应于堆栈的变量,即由一个线程拥有?
编辑:
由一个线程拥有,我想表达该变量未公开.地址不给其他任何东西.它不被任何其他线程使用.
考虑两个线程,T1 和 T2,它们分别存储和加载一个原子整数 a_i。让我们进一步假设,这家店在执行前正在执行的负荷启动。以前,我的意思是绝对的时间意义。
T1 T2
// other_instructions here... // ...
a_i.store(7, memory_order_relaxed) // other instructions here
// other instructions here // ...
a_i.load(memory_order_relaxed)
// other instructions here
Run Code Online (Sandbox Code Playgroud)
是否保证T2在加载后看到值7?
引用gnu:
实际上,您可以假设 int 是原子的。您还可以假设指针类型是原子的;非常方便。这两个假设在 GNU C 库支持的所有机器上以及我们所知的所有 POSIX 系统上都是成立的。
这怎么可能?我见过的与锁相关的所有示例都是用int计数器制作的,例如https://www.delftstack.com/howto/c/mutex-in-c/。