首页 文章

C 0x内存模型和推测性加载/存储

提问于
浏览
23

所以我正在阅读有关即将推出的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"

Sutter, "Prism: A Principle-Based Sequential Memory Model for Microsoft Native Code Platforms", N2197

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 回答

  • 5

    如果 y==2 ,另一个线程修改或读取 x ,原始样本中的竞争条件如何?此线程永远不会触及 x ,因此其他线程可以自由地执行此操作 .

    但是对于重新排序的版本,我们的线程修改 x ,如果只是暂时的,所以如果另一个线程也操纵它,我们现在有一个竞争条件,之前没有 .

  • 8

    我希望编译器只用 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想要支持这样的代码似乎是合理的:

    if (single_threaded) {
        x = 17;
    } else {
        sendMessageThatSafelySetsXTo(17);
    }
    

    显然,你不希望转变为:

    tmp = x;
    x = 17;
    if (!single_threaded) {
        x = tmp;
        sendMessageThatSafelySetsXTo(17);
    }
    

    这与您的示例中的转换基本相同,但只有2个案例,而不是足以让它看起来像一个良好的代码大小优化 .

相关问题