The Happens-Before Relation

JUL 02, 2013

http://preshing.com/20130702/the-happens-before-relation/


Happens-before是一个现代计算机科学术语,对于描述C++11,Java,Go甚至LLVM之后的software memory model是有帮助的。

你可以在上面的每一种语言规范中找到happens-before关系的定义。这些规范给出的定义本质上是相同的,虽然描述方式不同。大约说来,通用的定义可以这么描述:
使用A和B代表多线程程序的操作,如果A happens-before B,那么A操作的内存影响将在B被执行之前对执行B的线程可见。


当你考虑memory reordering可以在很多方式上使lock-free编程变得复杂时,A happens-before B保证是理想的一个。有好几种方式可以获得这种保证,不同的语言也不一样——尽管,所有的语言都必须依赖processor层提供的那些相同机制。

不管你使用的是哪种编程语言,有一点是相同的:如果操作A和操作B被同一个线程执行,并且程序中,A的语句在B的语句之前,那么A happens-before B。这其实就是前面提到过的memory ordering的首要原则。

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标准说,在其它方法中,你可以在不同线程之间的操作中使用acquire and release语义来实现。我会在下面的synchronized-with中展开谈论。

我非常确信这个关系的名字可能会引起一些困惑。有必要整理清楚:happens-before关系,基于上面的定义,和A实际上在B之前发生并不是一回事!特别的:
1 A happens-before B不意味着A在B之前发生
2 A在B之前发生不意味着A happens-before B

这些描述看起来自相矛盾,其实不然。我会在下面几节试着解释。记住,happens-before是操作之间的形式关系(formal relation),由语言规范定义;它独立于时间上的概念而存在。这和我们通常说“A在B之前发生”时表示的意思是不同的,参考真实世界的事件的顺序,立刻;在本篇,我将小心的加上-,以区分于后者。

Happens-Before Does Not Imply Happening Before

这是一个具有happens-before关系的例子,而实际上不一定就按照这个顺序发生。下面的代码执行:(1)a store to A, 接下来是(2)a store to B。根据程序顺序的规则,(1) happens-before (2)。

int A = 0;
int B = 0;
void foo() {
    A = B + 1;              // (1)
    B = 1;                  // (2)
}
然而,如果我们在GCC下使用-O2来编译这段代码,编译器会执行一些指令重排序。结果就是,当我们在汇编语言层面来逐步调试运行时,可以清晰地看到第二条机器指令,store to B已经结束了,而store to A还没有,换句话说,(1)没有真正的在(2)之前发生!


那么happens-before关系是否被破坏了呢?让我们看看,根据定义,(1)的内存效果必须在(2)被执行之前可见。换句话说,store to A必须有机会影响到store to B。

在本例中,尽管store to A并没有实际上影响到store to B。即使(1)的效果已经可见了,(2)依然会做出同样的行为,这和(1)的影响可见是一样的。因此,并没有违反happens-before规则。我承认,这解释比较dicey,但是我自信它和所有语言规范中happens-before的意义相一致。

Happening Before Does Not Imply Happens-Before

这是一个是按照指定顺序发生的操作,但并不构成happens-before关系的例子。下面的代码中,想象一个publishMessage线程,另一个线程是consumeMessage。因为我们并发的操作共享变量,简化一下,假设下面int的load和store都是原子的。因为程序顺序,(1)和(2)之间存在happens-before关系,此外(3)和(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关系仅仅存在于语言标准指明它存在的地方。而对于这些plain load和store,C++11标准并没有规定(2)和(3)之间构成了happens-before关系,即使(3)读取到了(2)写入的值。

更进一步,因为(2)和(3)之间不存在happens-before关系,(1)和(4)之间也没有。因此(1)和(4)的内存交互是可以被重排序的,或者是编译器指令重排序,或者是processor自己的memory reordering,比如(4)结束时打印0,即使(3)读到的是1。

这篇文章并没有什么新东西,我们在前面介绍weakly-ordered CPU时已经介绍过了。后面几句不翻译了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值