Happens-Before

目录

Happens-Before 关系

A happens-before B 并不意味着 A happening before B

A happening before B 并不意味着 A happens-before B


Happens-Before 关系

Happens-before是一个现代计算机科学术语,它有助于描述C ++ 11,Java,Go甚至LLVM背后的软件内存模型。

您会在上述每种语言的规范中找到Happens-before关系的定义。省事的是,这些规范中给出的定义基本相同,尽管每个规范都有不同的说法。粗略地说,通用定义可以表述如下:

设A和B表示由多线程进程执行的2个操作。如果A Happens-before B,那么在执行B之前,A对内存操作的结果对于执行B的线程变得可见。

当您考虑要简化无锁编程中复杂的存储器重排序时,A Happens-before B的保证是一个理想的方式。有几种方法可以获得这种保证,从一种编程语言到下一种编程语言略有不同 - 尽管显然,所有语言都必须依赖于处理器级别的相同机制。

无论您使用哪种编程语言,它们都有一个共同点(程序排序规则):如果操作A和B由同一个线程执行,并且A的语句在程序顺序中(program order)出现在B的语句之前,则A happens-before B。

int A, B;

void foo()
{
    // This store to A ...
    A = 5;

    // ... effectively becomes visible before the following loads. Duh!
    B = A * A;
}

这不是实现Happens-before关系的唯一方式(上面的共同点)。C ++ 11标准规定,除了其他方式之外,您还可以使用获取和释放语义(类似于锁的获取释放语义)在不同线程中的操作之间实现它。

我很确定这种关系的名称可能会导致某些人感到困惑。值得澄清:在上面给出的定义下,A happens-before B的关系与 实际上的A happening before B不同!特别是:

  1. happens-before B 并不意味着 A happening before B.
  2. A happening before B 并不意味着 A happens-before B.

这些陈述可能看似矛盾,但事实并非如此。我将尝试在以下部分解释它们。 请记住,happens-before是由一系列语言规范定义,与我们通常所说的“A happening before B”的意思不同。

happens-before B 并不意味着 A happening before B

这是一个具有happens-before关系的例子,但实际上并没有按此顺序发生。以下代码执行(1)(存储到A),然后执行(2)(存储到B).根据程序排序规则, (1) happens-before (2)。

int A = 0;
int B = 0;

void foo()
{
    A = B + 1;              // (1)
    B = 1;                  // (2)
}

但是,如果我们用GCC使用-O2选项编译此代码,编译器将执行一些指令重新排序。 当我们在调试器中的逐步执行生成的反汇编代码时,我们清楚地看到在第二个机器指令之后,B的存储(2)已经完成,但是A的存储没有完成。换句话说,(1)实际上并不发生在(2)之前!而是(2)发生在(1)的中间。

那么happens-before的关系是否被违反?让我们来看看,根据定义,(1)对内存的操作结果必须在执行(2)之前有效地可见。换句话说,对A的存储如果能影响的话一定会影响对B的存储。在这种情况下,对A的Store实际上并不会影响到对B的Store。即使(1)对A的Stroe对于(2)真的可见,这实际上与图中可见的效果相同。因此不违反。我承认,这个解释有点冒险,但我相信它与所有那些语言规范中之前发生的含义一致。

A happening before B 并不意味着 A happens-before B

这是一个明确以特定顺序发生而不构成happens-before关系的操作示例。在下面的代码中,假设一个线程调用publishMessage,而另一个线程调用consumeMessage。由于我们同时操作共享变量,让我们保持简单,并假设int的普通加载和存储都是原子的。由于程序排序规则,(1)happens-before (2),(3)happens-before (4)。

int isReady = 0;
int answer = 0;

void publishMessage()
{
    answer = 42;                      // (1)
    isReady = 1;                      // (2)
}

void consumeMessage()
{
    if (isReady)                      // (3) <-- Let's suppose this line reads 1
        printf("%d\n", answer);       // (4)
}

更近一步,假设在运行时,标记为(3)的行最终读取1(即在另一个线程中存储在第(2)行的值),在这种情况下,我们知道(2)必须在(3)之前发生。但这并不意味着在(2)和(3)之间存在happens-before的关系!

happens-before的关系只存在于语言标准所说的存在之处。由于这些是普通的加载和存储,因此C ++ 11标准没有引入(2)和(3)之间的happens-before关系的规则,即使(3)读取了由(2)写入的值也是如此。

再进一步,因为(2)和(3)之间没有happens-before的关系,所以(1)和(4)之间也没有happens-before的关系(如果(2)、(3)有happens-before,那么根据happens-before的传递规则,(1)(4)就有happens-before关系)。因此,(1)和(4)的可以重新排序,不论是编译器指令重排序,还是处理器本身的重排序,从而使得(4)最终打印“0”,即使(3)读取了1 。

这篇文章并没有真正展现出什么新东西。我相信happens-before关系和实际操作顺序之间存在的模糊性是使低级别无锁编程如此棘手的部分原因。如果没有别的,这篇文章应该证明happens-before关是一个有用的保证,但在线程之间不好使(没有happens-before的偏序保证???)。

译完。

原文:https://preshing.com/20130702/the-happens-before-relation/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值