Appearance
题目
属于同一进程的两个线程 thread1 和 thread2 并发执行,共享初值为 0 的全局变量 x。thread1 和 thread2 实现对全局变量 x 加 1 的机器级代码描述如下:
thread1
mov R1, x // (x) → R1
inc R1 // (R1) + 1 → R1
mov x, R1 // (R1) → xthread2
mov R2, x // (x) → R2
inc R2 // (R2) + 1 → R2
mov x, R2 // (R2) → x在所有可能的指令执行序列中,使 x 的值为 2 的序列个数是( )。
错因
A
只想到了"thread1 完整跑完再 thread2 完整跑完"这一种顺序,忘了反过来"thread2 先 thread1 后"也是一种合法的指令交错。两个线程对称——thread1 全跑完 x=1,thread2 再读 R2=1 加 1 写回 x=2;反过来 thread2 全跑完 x=1,thread1 再读 R1=1 加 1 写回 x=2,两条路径都成立。
C
可能注意到了"两个串行序列各 1 种,再凑一种交错也行",但实际上任何穿插进去都会让 x=1。因为穿插意味着第二个线程的 mov R,x 发生时第一个线程的 mov x,R 还没写回——两个线程各自读到的 x 都是 0,inc 后两个寄存器都是 1,最后写哪个 R 都写 1,总数会"丢一次"。所以中间不存在第三种 x=2 的情况。
D
把"6 条指令在保持各自顺序前提下的交错总数"直接代入了,记成 4。实际交错总数是 种,其中只有 2 种是完全串行的、能让 x=2,其余 18 种都因为读-改-写交错而让 x=1。把"种类多"等同于"答案大"是这题的陷阱。
总解析
两个线程各 3 条指令(M:mov R,x;I:inc R;W:mov x,R)。要让 x=2,最后写入的那条 W 写的值必须是 2,意味着对应的寄存器 R 在写入前是 2,意味着该 R 之前的 M 读到的 x 是 1,意味着另一个线程的 W 必须已经完成——而那条 W 写 1 又要求它对应的 M 读到 0,也就是发生在另一个线程的 W 之前。
倒推下来唯一可行的相对顺序是:
一个线程的 M、I、W 三条全部完成 → 另一个线程的 M、I、W 三条才开始。
所以"全交错"是不行的,必须完全串行。
枚举:
| 顺序 | x 演化 | 结果 |
|---|---|---|
M1 I1 W1 M2 I2 W2(thread1 先全跑) | 0→0→1→1→1→2 | x = 2 ✓ |
M2 I2 W2 M1 I1 W1(thread2 先全跑) | 0→0→1→1→1→2 | x = 2 ✓ |
其他任何穿插(如 M1 M2 I1 W1 I2 W2) | 都至少有一对 M 在对方 W 之前 | x = 1 ✗ |
只有上面 2 种串行序列让 x=2。
最终答案是 B。