- 编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序,对于一个共享变量,写写、写读、读写存在数据依赖关系
- 不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑
下面看看重排序是否会改变多线程程序的执行结果
class ReorderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
Public void reader() {
if (flag) { // 3
int i = a * a; // 4
}
}
flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入呢?
答案是:不一定能看到。
由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。
操作3和4重排序示意图
- 在程序中,操作3和操作4存在控制依赖关系。当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜测(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,然后把计算结果临时保存到一个名为重排序缓冲(Reorder
Buffer,ROB)的硬件缓存中。当操作3的条件判断为真时,就把该计算结果写入变量i中。 - 操作3和4的重排序遵守as-if-serial语义,但是破坏了多线程程序的语义
- 操作3和4存在控制依赖关系,as-if-serial语义允许对存在控制依赖的操作做重排序的原因,但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。