所以我正在阅读有关即将推出的C 0x标准的内存模型 . 但是,我对允许编译器做什么的一些限制有点困惑,特别是关于推测性加载和存储的限制 .
首先,一些相关的东西:
Hans Boehm's pages about threads and the memory model in C++0x
Boehm, "Threads Cannot be Implemented as a Library"
Boehm and Adve, "Foundations of the C++ Concurrency Memory Model"
Boehm, "Concurrency memory model compiler consequences", N2338
现在,基本思想基本上是"Sequential Consistency for Data-Race-Free Programs",这似乎是编程的简易性和允许编译器和硬件优化机会之间的妥协 . 如果不对不同线程对相同存储器位置的两次访问进行排序,则至少有一个存储到存储器位置,并且它们中的至少一个不是同步动作,则定义数据争用 . 这意味着对共享数据的所有读/写访问必须通过某些同步机制,例如互斥体或对原子变量的操作(嗯,可以对原子变量进行操作,只为专家提供放松的内存排序,但默认提供为顺序一致) .
鉴于此,我对普通共享变量上的虚假或推测性加载/存储的限制感到困惑 . 例如,在N2338中我们有例子
switch (y) {
case 0: x = 17; w = 1; break;
case 1: x = 17; w = 3; break;
case 2: w = 9; break;
case 3: x = 17; w = 1; break;
case 4: x = 17; w = 3; break;
case 5: x = 17; w = 9; break;
default: x = 17; w = 42; break;
}
不允许编译器转换为
tmp = x; x = 17;
switch (y) {
case 0: w = 1; break;
case 1: w = 3; break;
case 2: x = tmp; w = 9; break;
case 3: w = 1; break;
case 4: w = 3; break;
case 5: w = 9; break;
default: w = 42; break;
}
因为如果y == 2,则存在对x的虚假写入,如果另一个线程同时更新x,则可能是一个问题 . 但是,为什么这是一个问题呢?这是一场数据竞赛,无论如何都是被禁止的;在这种情况下,编译器只是通过写入x两次使其变得更糟,但即使单个写入也足以进行数据竞争,不是吗?即一个正确的C 0x程序需要同步访问x,在这种情况下,不再有数据竞争,虚假存储也不会是一个问题?
我同样对N2197中的例3.1.3以及其他一些例子感到困惑,但也许对上述问题的解释也可以解释这一点 .
EDIT: The Answer:
推测商店存在问题的原因在于,在上面的switch语句示例中,程序员可能已选择仅在y!= 2时有条件地获取锁定保护x . 因此,推测商店可能会引入不存在的数据竞争原始代码,因此禁止转换 . 同样的论点也适用于N2197中的例3.1.3 .
2 回答
如果
y==2
,另一个线程修改或读取x
,原始样本中的竞争条件如何?此线程永远不会触及x
,因此其他线程可以自由地执行此操作 .但是对于重新排序的版本,我们的线程修改
x
,如果只是暂时的,所以如果另一个线程也操纵它,我们现在有一个竞争条件,之前没有 .我希望编译器只用
tmp = x; x = 17; x = tmp;
替换no-op .假设线程A想要假设没有其他线程修改x . 如果y为2,并且将值写入x,然后将其读回,它将返回其写入的值,这是合理的 . 但是如果线程B同时执行你的第二位代码,那么线程A可以写入x并稍后读取它,并返回原始值,因为线程B在“写入之前”保存并在“之后”恢复 . 或者它可以返回17,因为线程B在写入之后存储了17“,并且在”线程A读取之后再次存储tmp“ . 线程A可以执行它喜欢的任何同步,但它没有帮助,因为线程B不同步 . 它不同步的原因(在y == 2的情况下)是它不使用x . 因此,特定位代码“使用x”的概念对于线程模型是否重要,这意味着当“不应该”时,不允许编译器更改代码以使用x .
简而言之,如果您提出的转换被允许,引入虚假写入,则永远不可能分析一些代码并得出结论它不会修改x(或任何其他内存位置) . 因此,有许多方便的习惯用法是不可能的,例如在没有同步的情况下在线程之间共享不可变数据 .
因此,虽然我对"data race"的定义,但我认为它包含了允许程序员假设某个对象未被写入的一些条件,并且这种转换会违反这些条件 . 我推测,如果y == 2,那么你的原始代码连同并发代码:
x = 42; x = 1; z = x
在另一个线程中,没有被定义为数据竞争 . 或者至少如果它是数据竞争,它不是允许z以17或42结束的值 .考虑到在这个程序中,y中的值2可能用于表示“还有其他线程正在运行:不要修改x,因为我们在这里不同步,因此会引入数据竞争” . 也许是原因根本没有同步,在y的所有其他情况下,没有其他线程可以访问x . 对我来说,C 0x想要支持这样的代码似乎是合理的:
显然,你不希望转变为:
这与您的示例中的转换基本相同,但只有2个案例,而不是足以让它看起来像一个良好的代码大小优化 .