Understanding of causality constraint Java memory model
Welcome to the discussion
Welcome to technical exchange group 186,233,599 discuss and exchange, I also welcome the focus on public numbers: Wind & Fire said.
Understood specifications
This part of the contents of the abstract, the first is the definition of the beginning, as follows
Content should be underlined in red key to understanding. First, E is a specific execution sequence, which instruction set A and set inside for the presence of A PO, SO, SW, HB sorting configuration. C <sub> i </ sub> is contained in a subset of E, that is to say C <sub> i directive </ sub> in all execution instruction E in the presence of A collection.
Look at the second, third and fourth red line (ignoring the case of A is an infinite set of infinite set of threads means that there was an infinite loop, never-ending, this is not a reasonable procedures), these three together appreciated, may be considered C <sub> i </ sub> increase an instruction constitutes the C <sub> i + 1 </ sub>, and then form a new a <sub> i + 1 </ sub> . The new A <sub> i + 1 </ sub> binding PO, SO, SW, HB became the new relationship E <sub> i + 1 </ sub>.
Next look at the definition of subsequent
这 5 个约束合在一起实际上是说明如何构成一个 C 集合。简单而言,C<sub>i</sub> 是 A<sub>i</sub>的一个子集,并且这个子集和执行轨迹E<sub>i</sub> 拥有相同的 HB,SO 关系,且 C<sub>i</sub> 中写入操作的写入值和 E<sub>i</sub> 中相同,C<sub>i</sub> 中对写入值的观察结果和 E<sub>i</sub> 中相同。而 E<sub>i</sub> 是逐步构成 E 的第 i 步骤,最终 E~n~ 等同于 E 。这实际上约束了 C 是如何构成的,它并不是凭空而来,而是不断的将 E 中的指令添加到 C 之中,并且这些添加的指令都和 E 拥有相关的观察效果,写入值,以及偏序关系。通过确保一系列的 E<sub>i</sub> 都是合法的,最终确定 E 是合法的。
再来剩下的两条规则
第 6 条规则定义了要往集合 C 中添加读取指令时,该指令的观察结果。换句话说集合 C~i-1~ 中的写入操作产生的效果,能被任意未添加到该集合中的写入操作观察到。
第 7 条规则和上面的 5 条规则相同,也是在明确在集合 C 中产生的观察效果在执行轨迹 E 中也是存在的。
7个规则都在描述在集合 C 中的写入值,读取结果,指令排列顺序都是和 E 等同,因此通过不断的构建 C<sub>i</sub> ,最终 C~n~ 等同于 A~n~ ,再加上在 C~n~ 中的写入值,读取结果,指令排列顺序,就构成了最终的 E 。而如果这一系列的 C<sub>i</sub> 都是“合法”的话,则最终的执行轨迹 E 也是合法的。
当我们需要向集合新增一个读取指令时,其读取到的值只能在该集合中的写入值。提交指令到集合中时,如果存在HB 关系 或依赖关系的语句阻止其提交,则提交不能成功。
例子练习
例子1
首先来看一段代码,如下
nonvolatile global int a = 0, b = 0;
ThreadA()
{
int aL = a;
if(aL == 1) b = 1; } ThreadB() { int bL = b; if(bL == 1) a = 1; }
对于内存模型而言,其只关注操作内存的指令,在执行轨迹 E 的 A 集合的内容是
int aL = a;
b = 1;
bL = b;
a = 1;
由于int aL = a;
和a = 1;
不存在 HB 关系,因此可以通过数据竞争的方式读取到该写入值,也就是aL
的值是 1 。bL = b;
和b = 1;
不存在 HB 关系,因此可以通过数据竞争的方式读取到该写入值,也就是bL
的值是 1 。
一个读取操作可以读取的到值或者是通过 HB 关系得到,或者是通过数据竞争得到。也就是说,在没有 HB 关系阻止该读取结果时,该读取结果是允许的,这被称之为 HB 一致性。显然,上面的输出结果aL==bL==1
是符合 HB 一致性的。
然而从顺序一致性执行的角度而言,这种输出结果就好像是因为aL
读取到线程 B 写入的值,产生了b=1
的结果,而这个结果导致了bL==1
的结果,而这个结果导致了a=1
的结果,而a==1
导致了aL=1
。形成了一个循环,显然这是违背直觉的。而 JMM 中因果性的要求就是用来判定这种执行轨迹是否合法的依据。
再用因果性分析这个执行之前,我们先看另外一个更简单一些的例子,代码如下
0: x == y == 0
Thread 1:
1: r1 = x;
2: y = 1;
Thread 2:
3: r2 = y;
4: x = r2;
这个例子反复出现,显然我们知道r1==r2==1
是一个合法的输出结果。因为重排序的原因,y=1
被执行,而后r2=y
观察到这个写入,x=r2
同样得到值 1,r1=x
观察到这个写入。下面我们使用因果性来分析这个执行轨迹。
首先我们将集合 C 中添加指令 2 。与指令 2 存在 HB 关系的是指令 0 和 1 。他们都不会阻止指令 2 的发生,因此指令 2 被允许添加到集合 C 中,此时有 C<sub>1</sub> 。
我们用 W(variable,value) 来表达对一个变量 variable 写入 value 的值,用 R(variable,value) 表达从变量 variable 中读取到 value 的值。
因此目前我们有 C<sub>0</sub>= {W(x,0),W(y,0)} 的初始状态。而 C<sub>1</sub>=C<sub>0</sub> U {W(y,1)} 。然后我们添加指令 3 到 C<sub>1</sub> 中,按照 HB 关系,指令观察到的值应该是指令 0 写入的。但是同时,它也允许观察到提交集合中已经写入的值,也就是存在于提交集合中指令 2 的写入值。因此我们有 C<sub>2</sub>=C<sub>1</sub> U {R(y,1)} 。
接着我们提交指令 4 ,显然此时有 C<sub>3</sub>=C<sub>2</sub> U {W(x,1)} 。
最后我们提交指令 1,按照 HB 关系,此时允许的观察值由指令 0 写入,也就是 0 。与上述相同,允许其观察到在提交集合中的写入值,因此 C<sub>4</sub>=C<sub>3</sub> U {R(x,1)} 。
C<sub>4</sub>=A<sub>4</sub>,C<sub>4</sub> 中的写入值,读取值,排列顺序都与 E<sub>4</sub> 相同,也与 E 相同。因此判定该执行轨迹是合法的,其表现是符合 JMM 要求的。
接下来我们回到最开始的例子,如果我们要得到bL==1
的结果,意味着我们需要执行b=1
这个指令。而要执行该指令,我们需要执行int aL = a;
指令并且读取到值 1 。注意,因果性的判断是需要考虑条件判断因素的,而 HB 一致性则不考虑,它仅仅是提取所有的可能执行指令并且假定其执行。
从提交集合的角度出发,我们需要提交int aL = a;
实际上是想提交 R(a,1) 。但是 C<sub>0</sub>={W(x,0),W(y,0)} ,C<sub>1</sub> 中的读取操作的读取值只能由 C<sub>0</sub> 中的写入造成,因此 R(a,1) 无法被提交到 C<sub>1</sub> 中。这就意味着达成a==b==1
的提交集合不合法,因此对应的执行轨迹也是非法的。所以这种结果不被 JMM 允许。
例子2
int /* non-volatile */ a;
int /* non-volatile */ b;
ThreadA()
{
int tmp = a; b = tmp; } ThreadB() { int tmp = b; a = tmp; }
For the above code, a==b==1
the result is in line with HB consistency. This situation appears to occur in sub-1 ratio is more difficult to understand, because the value 1 appears to be unfounded. However, we first assume that int tmp = a;
reading to the 1, then b = tmp;
will write 1. And int tmp = b;
it reads this value, which can lead to a = tmp;
write the value 1, just to meet int tmp = a;
the reading needs to 1. HB consistency, if the relationship does not prevent a HB is read, the read is allowed, which is considered to be int tmp = a;
read to the future value of the data written on a competitive basis. In this manner consideration, this example is substantially similar to example 1 of the second example.
But obviously, this result is counterintuitive, as a result JMM is also prohibited. We use causal approach to analysis.
First, we obviously have C <sub> 0 </ sub > = {W (a, 0), W (b, 0)}. Then we submitted int tmp = a;
(can not be submitted b = tmp;
because of int tmp = a;
the presence of its dependencies, and there is HB relationship). The C <sub> 0 </ sub > content, apparently only allowed to submit this case R (a, 0), or R (b, 0). By causality analysis, we have the example of the legitimate output only a==b==1
.
to sum up
Speaking informally, it may be considered to get through a combination of JMM HB consistency and causality requirements. These two constraints together, only the JMM guarantee programmers: If a program is properly synchronized, the program performance for sequential consistency.
Source: only one truth