我试图通过以下简单示例了解指令的重新排序:
int a;
int b;
void foo(){
a = 1;
b = 1;
}
void bar(){
while(b == 0) continue;
assert(a == 1);
}
众所周知,在此示例中,如果一个线程执行foo
,而另一个线程执行bar
,则断言可能会失败。但是我不明白为什么。我咨询了Intel manual Vol. 3A, 8.2.2,发现了以下内容:
对存储器的写入不会与其他写入一起重新排序,因为 下列例外:
-使用以下命令执行的流式存储(写入) 非临时移动指令(MOVNTI,MOVNTQ,MOVNTDQ,MOVNTPS和 MOVNTPD);和
-字符串操作(请参见第8.2.4.1节)。
这里没有字符串操作,也没有注意到NT
移动指令。那么...为什么可以对写进行重新排序?
还是记忆很重要
对 内存 的写入没有重新排序
?因此,当我们缓存了a
和b
时,写到主存储器中的不是写到主存储器,而是写到了主存储器。
答案 0 :(得分:4)
如果一个线程正在运行foo
,而另一个线程正在运行bar
,则程序的行为将是未定义。
不允许您同时对非原子变量(例如int
)进行读写。
因此在这种情况下可以进行指令排序。
答案 1 :(得分:3)
您的前提是错误的。只有编译时重新排序才能在x86 1 上破坏此示例。
x86 asm存储是发行存储。它们只能按程序顺序从存储缓冲区提交到L1d缓存。
在看到a
之后, b=1
不能仍然处于共享状态;这意味着运行foo
的线程让其存储顺序混乱。这就是对内存的写入不会与其他写入操作一起重新排序,意味着会将其存储到可缓存的内存中。
如果再次由运行foo
的线程的RFO使它处于共享状态 ,则它将具有a
的更新值。
脚注1.当然,自旋循环将优化为if (b==0) infinite_loop
,因为数据争用UB使编译器可以提升负载。参见MCU programming - C++ O2 optimization breaks while loop。
您似乎在询问C规则,同时假设代码会天真地/直接转换为x86 asm。您可以使用轻松的原子来实现,但不能使用volatile
来实现,因为volatile
访问不能与其他volatile
访问重新排序(在编译时)。