首先我想谈一下我对此的一些理解,如有错误请指正。
MFENCE在x86中可以保证全屏障顺序一致性可防止 STORE-STORE、STORE-LOAD、LOAD-STORE 和 LOAD-LOAD 重新排序
这是根据维基百科的说法。
std::memory_order_seq_cst不保证防止 STORE-LOAD 重新排序。
这是根据Alex 的回答,“负载可能会通过早期存储重新排序到不同位置”(对于 x86),并且 mfence 不会总是被添加。
a是否std::memory_order_seq_cst表示顺序一致性?根据第2/3点,我认为这似乎不正确。std::memory_order_seq_cst仅当以下情况时才表示顺序一致性
MFENCE添加到任一LOAD或STORE否则仍有可能重新订购。
根据@LWimsey的评论,我在这里犯了一个错误,如果 和LOAD都是STORE,memory_order_seq_cst则没有重新排序。Alex 可能指出使用非原子或非 SC 的情况。
std::atomic_thread_fence(memory_order_seq_cst)总是产生一个完整的屏障
这是根据Alex的回答。所以我总是可以替换asm volatile("mfence" ::: "memory")为std::atomic_thread_fence(memory_order_seq_cst)
这对我来说很奇怪,因为memory_order_seq_cst原子函数和栅栏函数之间的用法似乎有很大不同。
现在我在MSVC 2015的标准库的头文件中找到这段代码,它实现了std::atomic_thread_fence
inline void _Atomic_thread_fence(memory_order _Order)
{ /* …Run Code Online (Sandbox Code Playgroud) 我看到g ++生成一个简单的movfor x.load()和mov+ mfencefor x.store(y).考虑这个经典的例子:
#include<atomic>
#include<thread>
std::atomic<bool> x,y;
bool r1;
bool r2;
void go1(){
x.store(true);
}
void go2(){
y.store(true);
}
bool go3(){
bool a=x.load();
bool b=y.load();
r1 = a && !b;
}
bool go4(){
bool b=y.load();
bool a=x.load();
r2= b && !a;
}
int main() {
std::thread t1(go1);
std::thread t2(go2);
std::thread t3(go3);
std::thread t4(go4);
t1.join();
t2.join();
t3.join();
t4.join();
return r1*2 + r2;
}
Run Code Online (Sandbox Code Playgroud)
其中根据https://godbolt.org/z/APS4ZY go1和go2被翻译成
go1():
mov BYTE PTR x[rip], 1 …Run Code Online (Sandbox Code Playgroud) 超线程技术是英特尔推出的一种同步多线程技术.
这些资源包括执行引擎,缓存和系统总线接口; 资源共享允许两个逻辑处理器更有效地相互协作,并允许停滞的逻辑处理器从另一个逻辑处理器借用资源.
在具有超线程的Intel CPU中,一个CPU内核(具有多个ALU)可以在同一时钟执行来自2个线程的指令.两个线程共享:存储缓冲区,缓存L1/L2和系统总线.
但是如果两个线程在一个Core上同时执行,则thread-1存储原子值,而thread-2加载此值,将用于此交换的内容:共享存储缓冲区,共享缓存L1/L2还是通常的缓存L3?
如果两个线程来自同一个进程(相同的虚拟地址空间)和两个不同进程(不同的虚拟地址空间),会发生什么?
Sandy Bridge Intel CPU - 缓存L1:
低12位 - 对于确定当前设定数值很重要
4 KB - 标准页面大小
由于存储负载转发,某些负载指令能否在全局范围内不可见?换句话说,如果加载指令从存储缓冲区中获取其值,则它永远不必从高速缓存中读取。
通常说来,当从L1D缓存读取负载时,该负载在全局范围内可见,因此,未从L1D读取的负载应使其在全局上不可见。
假设变量a = 0
Processor1: a = 1
Processor2: print(a)
Run Code Online (Sandbox Code Playgroud)
Processor1首先执行它的指令,然后在下一个周期中,processor2读取变量以打印它。因此:
Processor2将暂停,直到高速缓存一致性操作完成,它将打印1
P1: |--a=1--|---cache--coherence---|----------------
P2: ------|stalls due to coherence-|--print(a=1)---|
time: ----------------------------------------------->
Run Code Online (Sandbox Code Playgroud)高速缓存一致性操作完成之前,processor2将运行,并且在此之前它将具有陈旧的内存视图。因此它将打印0?
P1: |--a=1--|---cache--coherence---|
P2: ----------|---print(a=0)---|----
time: ------------------------------->
Run Code Online (Sandbox Code Playgroud)
换句话说,在高速缓存一致性操作完成之前,处理器可以拥有过时的内存视图吗?
我一直在阅读有关x86内存模型如何工作以及屏障指令在x86上的意义,并与其他体系结构(例如ARMv8)进行比较。在x86和ARMv8架构中,内存模型都遵循(无双关),即传递性/累积性,即如果CPU 1看到CPU0的存储,而CPU2看到CPU1的存储,则只有在CPU1看到CPU0的存储时才会发生,然后CPU2还必须查看CPU0的存储。我指的示例是保罗·麦肯尼(Paul McKenney)著名论文6.1节中的示例1和2(尽管相关,但他最新的性能手册《http://www.puppetmastertrading.com/images/hwViewForSwHackers》中也存在同样的问题。 pdf格式)。如果我理解正确,那么x86使用商店队列(或商店订单缓冲区)对商店进行排序(以及其他微体系结构优化),然后使其成为全局可见(即写入L1D)。我的问题是x86拱(和其他拱)如何实现(微架构)传递性?存储队列确保按特定顺序使特定CPU的存储在全局范围内可见,但是又如何确保一个CPU进行的存储排序与其他CPU进行的存储排序呢?
x86 x86-64 cpu-architecture memory-barriers micro-architecture
我一直在阅读Jeff Preshing 的这篇关于 Synchronizes-With Relation 的文章,以及cpp 参考中的std::memory_order页面中的“Release-Acquire Ordering”部分,但我不太明白:
似乎标准有某种承诺,但我不明白为什么有必要。让我们以 CPP 参考中的示例为例:
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
Run Code Online (Sandbox Code Playgroud)
参考文献解释说:
如果线程 A 中的原子存储标记为 memory_order_release,并且线程 B …
std::atomic<bool> x{ false };
std::atomic<bool> y{ false };
// thread 1
y.store(true, std::memory_order_seq_cst);
x.store(true, std::memory_order_release);
// thread2
while (!x.load(std::memory_order_relaxed);
assert(y.load(std::memory_order_seq_cst)); // !!!
Run Code Online (Sandbox Code Playgroud)
断言会失败吗?我的理解是:虽然读取x是“放松的”,但一旦“线程2”看到“线程1”的写入,它就看不到y,false因为写入y发生在写入之前x。
内存顺序是从现实生活中的案例复制的,对于这个示例来说可能会更弱,但我没有改变它,以免错过任何微妙之处。
我有时会在关于内存排序的教程中看到术语"完全内存屏障",我认为这意味着以下内容:
如果我们有以下说明:
instruction 1
full_memory_barrier
instruction 2
Run Code Online (Sandbox Code Playgroud)
然后instruction 1不允许重新排序到下面full_memory_barrier,并且instruction 2不允许重新排序到上面full_memory_barrier.
但是完全内存屏障的反面是什么,我的意思是有什么像"半内存屏障"只能阻止CPU在一个方向上重新排序指令?
如果有这样的记忆障碍,我没有看到它的意思,我的意思是如果我们有以下指示:
instruction 1
memory_barrier_below_to_above
instruction 2
Run Code Online (Sandbox Code Playgroud)
假设这memory_barrier_below_to_above是一个阻止instruction 2重新排序到上面的内存屏障memory_barrier_below_to_above,因此不允许以下内容:
instruction 2
instruction 1
memory_barrier_below_to_above
Run Code Online (Sandbox Code Playgroud)
但是允许以下内容(这使得这种类型的内存屏障毫无意义):
memory_barrier_below_to_above
instruction 2
instruction 1
Run Code Online (Sandbox Code Playgroud) 我想在原子和非原子操作之间使用独立的内存屏障(我认为无论如何它都不重要)。我想我了解存储屏障和加载屏障的含义以及 4 种可能的内存重新排序;LoadLoad, StoreStore, LoadStore, StoreLoad.
但是,我总是发现获取/释放概念令人困惑。因为在阅读文档时,acquire 不仅说到loads,还说到stores,而release 不仅说到stores,还说到loads。另一方面,普通负载障碍仅为您提供负载保证,而普通商店障碍仅为您提供商店保证。
我的问题如下。在 C11/C++11 中,将独立atomic_thread_fence(memory_order_acquire)视为负载屏障(防止LoadLoad重新排序)和atomic_thread_fence(memory_order_release)存储屏障(防止StoreStore重新排序)是否安全?
如果以上是正确的,我可以用什么来防止LoadStore和StoreLoad重新排序?
当然,我对可移植性感兴趣,我不在乎上述在特定平台上产生什么。