(编辑:只是为了说明:"缓存一致性"的问题是在不使用原子变量的情况下。(
有可能吗(单CPU情况:Windows可以在Intel/AMD/Arm CPU上运行(,线程1在核心1上运行,存储一个bool
变量(例如(,并保留在L1缓存中,线程2在核心n上运行,使用该变量,并查看内存中的另一个副本?
代码示例(为了演示这个问题,假设std::atomic_bool
只是一个普通的bool
(:
#include <thread>
#include <atomic>
#include <chrono>
std::atomic_bool g_exit{ false }, g_exited{ false };
using namespace std::chrono_literals;
void fn()
{
while (!g_exit.load(std::memory_order_acquire))
{
// do something (lets say it takes 1-4s, repeatedly)
std::this_thread::sleep_for(1s);
}
g_exited.store(true, std::memory_order_release);
}
int main()
{
std::thread wt(fn);
wt.detach();
// do something (lets say it took 2s)
std::this_thread::sleep_for(2s);
// Exit
g_exit.store(true, std::memory_order_release);
for (int i = 0; i < 5; i++) { // Timeout: 5 seconds.
std::this_thread::sleep_for(1s);
if (g_exited.load(std::memory_order_acquire)) {
break;
}
}
}
CPU缓存在我们在1上运行C++线程的内核之间始终是一致的,无论它们是在同一个包(多核CPU(中还是分布在具有互连的套接字中。这使得一旦写入线程的存储执行并提交到缓存,就不可能加载过时的值。作为操作的一部分,它将向系统中的所有其他缓存发送一个无效请求。
其他线程总是可以最终看到您对std::atomic
vars的更新,即使使用mo_relaxed
也是如此。这就是重点;如果std::atomic
不起作用,它将毫无用处。("最终"通常是大约40纳秒的线程间延迟;relaxed
对此并不更糟,它只是在大多数ISAs上需要seq_cst
等其他线程可见的存储之前,不会暂停后续内存操作的执行。硬件内存屏障除了提供必要的保证外,还会加快原子操作的可见性吗?没有,或者没有显著影响(
但如果没有std::atomic
,代码将被超级破坏,MCU编程的一个经典示例-C++O2优化在循环和多线程程序停留在优化模式但在-O0中正常运行时中断-编译器可以假设没有其他线程正在写入它正在读取的非原子var,因此它可以将实际负载从循环中提升出来,并将其保存在线程专用CPU寄存器中。所以它根本不是从一致缓存中重新读取。即CCD_ 10变为普通CCD_ 12全局的CCD_。
寄存器是线程专用的,在任何方面都不一致,所以用普通int
或bool
编写的代码即使在单处理器系统中也可以打破这种方式。上下文开关只是将寄存器保存/恢复到线程专用内核缓冲区,它们不知道代码使用寄存器的目的,因此永远不会产生将bool g_exit
从内存重新读取到线程寄存器的效果。事实上,在将while(!non_atomic_flag){}
优化为if(!non_atomic_flag) while(42){}
之后,代码甚至可能不会重新检查寄存器
(除了你的sleep_for
调用可能会阻止这种优化。它可能没有被声明为纯的,因为你不希望编译器优化对它的多次调用;时间是副作用。所以编译器必须假设对它的调用可以修改全局变量,从而从内存中重新读取全局变量(使用通过一致缓存的正常加载指令(。(。
另一个相关问题是:如果使用"memory_order_relaxed"进行检查,为什么要使用"memory_order_seq_cst"设置停止标志?
脚注1:支持std::thread
的C++实现只能在同一一致性域中的核心之间运行。在几乎所有的系统中,只有一个一致性域包括所有套接字中的所有内核,但节点之间具有非一致共享内存的巨大集群是可能的。
具有ARM微控制器核心共享内存但与ARM DSP核心不一致的嵌入式板也是如此。您不会在这两个核心上运行单个操作系统,也不会将在这些不同核心上运行的代码视为同一C++程序的一部分。
有关缓存一致性的更多详细信息,请参阅何时将volatile与多线程一起使用?