c++ - MESI协议(protocol)&std::atomic-是否可以确保所有写操作立即对其他线程可见?

标签 c++ cpu-architecture memory-model stdatomic mesi

关于std::atomic,C++ 11标准规定,在“合理的时间量”内,存储到原子变量的变量将对该变量的负载可见。

从29.3p13:

Implementations should make atomic stores visible to atomic loads within a reasonable amount of time.



但是,我很想知道在处理基于MESI缓存一致性协议(protocol)(x86,x86-64,ARM等)的特定CPU体系结构时实际发生了什么。

如果我对MESI协议(protocol)的理解是正确的,那么一个内核将始终立即读取另一个内核先前写入/正在写入的值,可能是通过监听它来进行的。 (因为写一个值意味着发出一个RFO请求,从而使其他缓存行无效)

这是否意味着当线程A将值存储到std::atomic中时,在该原子上连续加载的另一个线程B实际上总是会观察到A在MESI体系结构上写入的新值吗? (假设没有其他线程对该原子执行操作)

“成功”是指线程A发布原子存储之后。 (修改顺序已更新)

最佳答案

我将回答在实际CPU上的实际实现中会发生什么,因为仅基于标准的答案几乎不能说出任何有关时间或“即时性”的有用信息。

MESI只是ISO C++没什么可说的实现细节。 ISO C++提供的保证仅涉及订单,而不涉及实际时间。 ISO C++特意是非特定的,以避免假定它将在“正常” CPU上执行。从理论上讲,在非一致性机器上实现显式刷新以实现商店可见性的实现是可能的(尽管对于发布/获取和seq-cst操作的执行可能是可怕的)

C++的时序不够具体,甚至不允许在单核协作式多任务系统上实现(无抢占),并且编译器偶尔会插入自愿的 yield 。 (无任何 Volatile 访问或I / O的无限循环是UB)。 在系统上实际上只能一次执行一个线程的系统上完全是可以实现的,假设您认为调度程序时间片仍然是“合理的”时间量。 (如果您屈服或以其他方式阻止,则更少。)

甚至ISO C++用来提供形式保证的形式化模型与硬件ISA定义其内存模型的方式也大不相同。 C++的形式保证完全是基于事前发生和与之同步,而不是“重新”对石蕊试纸或类似的东西进行排序。例如对于纯ISO C++形式主义,How to achieve a StoreLoad barrier in C++11?是不可能回答的。并且它可以显示C++保证的脆弱性。例如根据C++形式主义,所有seq_cst操作的总顺序的存在不足以暗示在此之前发生。但在具有连贯缓存且仅本地(在每个CPU内核内)重新排序的系统上,在现实生活中就足够了。

when a thread A stores a value into an std::atomic



这取决于您“开设”商店的意思。

如果要从存储缓冲区提交到L1d高速缓存,那么是的,那是存储在全局范围内可见的时刻,这是在使用MESI为所有CPU内核提供一致的内存 View 的普通计算机上。

尽管请注意,在某些ISA上,允许其他一些线程在通过缓存全局可见之前看到商店。 (即,硬件内存模型可能不是“多副本原子”,并且允许IRIW重排序。POWER是我所知道的唯一一个在现实生活中做到这一点的示例。有关硬件机制的详细信息,请参见Will two atomic writes to different locations in different threads always be seen in the same order by other threads?:存储已退休的aka的转发SMT线程之间的分级存储。)

如果您是指在本地执行,以便稍后在此线程中的加载可以看到它,则不会。 std::atomic可以使用比seq_cst弱的memory_order。

所有主流ISA的内存排序规则都很弱,足以允许存储缓冲区将指令执行与提交分离到缓存。在我们确定存储在正确的执行路径上之前,这还可以通过在执行之后为存储提供私有(private)的空间来进行推测性的无序执行。 (直到存储指令从后端的乱序部分退出,存储才可以提交到L1d,因此这被认为是非推测性的。)

如果要在其他加载之前等待存储对其他线程可见,请使用atomic_thread_fence(memory_order_seq_cst);。 (对于标准选择的C++-> asm映射的“常规” ISA,将编译成一个完整的障碍)。

在大多数ISA上,seq_cst存储(默认设置)也会使此线程中的所有以后的加载(和存储)停止,直到该存储在全局可见。但是在AArch64上,STLR是顺序发布的存储区,除非/直到STLR仍在存储缓冲区中时要执行LDAR(获取负载),否则以后的加载/存储区的执行不必暂停。假设AArch64硬件实际上以这种方式工作,而不是仅仅将其视为存储+全部障碍,这会尽可能弱地实现SC语义。

但是请注意,仅需要阻止以后的加载/存储。寄存器上ALU指令的乱序执行仍然可以继续。但是,例如,如果您由于FP操作的依赖链而期望某种时序效果,那么在C++中就不能依靠它了。

即使您确实使用seq_cst,所以在其他人看不到商店之前,此线程中什么也没有发生,但这仍然不是即时的。例如,实际硬件上的内核间延迟在现代主流英特尔x86上可以达到maybe 40ns的数量级。 (此线程不必在内存屏障指令上停顿那么长时间;其中的某些时间是另一个线程试图读取由该内核的RFO无效的行以获得独占所有权时的高速缓存未命中。)对于共享物理核心的L1d高速缓存的逻辑核心而言,便宜得多:What are the latency and throughput costs of producer-consumer sharing of a memory location between hyper-siblings versus non-hyper siblings?

关于c++ - MESI协议(protocol)&std::atomic-是否可以确保所有写操作立即对其他线程可见?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60292095/

相关文章:

c++ - openCV 滤波图像 - 用局部最大值替换内核

assembly - 现代 CPU 与 GPU 可以完成多少级流水线?

assembly - 哪种类型的汇编跳转指令最有用?

c++ - 以宽松的顺序读取共享变量 : is it possible in theory? 在 C++ 中是否可能?

c++ - 带 Eclipse CDT + MinGW + GLEW + GLFW 的 OpenGL : Undefined References

c++ - 如何保存特定输入的信息

c++ - 如何在 C++ 中的大端值和小端值之间进行转换?

c - 多处理器架构中的状态机事件生成

java - 由于内存不一致,线程能否观察到对象中的垃圾值?

C++11内存模型: why can't compiler move statements across load() operations during optimization?