『每周译Go』硬件内存模型

简介: 童话之终局

很久以前,当每个人都写单线程程序的时候,让程序运行得更快最有效的方法之一是坐下来袖手旁观。下一代硬件和编译器的优化结果可以让程序像以前一样运行,只是速度会更快。在这个童话般的年代,有一个判断优化是否有效的简单测试方法:如果程序员不能区分合法程序的未优化执行结果和优化执行的结果之间的区别(除了速度的区别),那么这个优化就是有效的。也就是说,有效的优化不会改变有效程序的行为。

几年前, 某个悲伤的日子,硬件工程师发现让单个处理器越来越快的魔法失效了。不过,他们发现了一个新的魔法,可以让他们创造出拥有越来越多处理器的计算机,操作系统使用线程抽象模型向程序员展示了这种硬件并行能力。这种新的魔法——多处理器以操作系统线程的形式提供并行能力——对硬件工程师来说效果更好,但它给编程语言设计者、编译器作者和程序员带来了严重的问题。

许多在单线程程序中不可见(因此有效)的硬件和编译器优化会在多线程程序中产生明显的结果变化。如果有效的优化没有改变有效程序的行为,那么这些优化应该被认为是无效的。或者现有程序必须被声明为无效的。到底是哪一个,怎么判断?

这里有一个类似C语言的简单示例程序。在这个程序和我们将要考虑的所有程序中,所有变量最初都设置为零。

// Thread 1           // Thread 2
x = 1;                while(done == 0) { /* loop */ }
done = 1;             print(x);

如果线程1和线程2都运行在自己专用处理器上,都运行到完成,这个程序能打印 0 吗?

看情况(It depends)。这取决于硬件,也取决于编译器。在x86多处理器上, 如果逐行翻译成汇编的程序执行的话总是会打印1。但是在ARM或POWER多处理器上,如果逐行翻译成汇编的程序可以打印0。此外,无论底层硬件是什么,标准编译器优化都可能使该程序打印0或进入无限循环。

“看情况”(It depends)并不是一个圆满的结局。程序员需要一个明确的答案来判断一个程序是否在新的硬件和新的编译器上能够正确运行。硬件设计人员和编译器开发人员也需要一个明确的答案,说明在执行给定的程序时,硬件和编译后的代码可以有多精确。因为这里的主要问题是对存储在内存中数据更改的可见性和一致性,所以这个契约被称为内存一致性模型(memory consistency model)或仅仅是内存模型(memory model)。

最初,内存模型的目标是定义程序员编写汇编代码时硬件提供的保证。在该定义下,是不包含编译器的内容的。25年前,人们开始尝试写内存模型 ,用来定义高级编程语言(如Java或C++)对用该语言编写代码的程序员提供的保证。在模型中包含编译器会使得定义一个合理模型的工作更加复杂。

这是关于硬件内存模型和编程语言内存模型的两篇文章中的第一篇。我写这些文章的目的是先介绍一下背景,以便讨论我们可能想要对Go的内存模型进行的改变。但是,要了解Go当前状况,我们可能想去哪里,首先我们必须了解其他硬件内存模型和语言内存模型的现状,以及他们采取的道路。

还是那句话,这篇文章讲的是硬件。假设我们正在为多处理器计算机编写汇编语言。程序员为了写出正确的程序,需要从计算机硬件上得到什么保证?四十多年来,计算机科学家一直在寻找这个问题的好答案。

顺序一致性

Leslie Lamport 1979年的论文《How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs》引入了顺序一致性的概念:

The customary approach to designing and proving the correctness of multiprocess algorithms for such a computer assumes that the following condition is satisfied: the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequence in the order specified by its program. A multiprocessor satisfying this condition will be called sequentially consistent.

为这种计算机设计和证明多处理算法正确性的通常方法假定满足下列条件:任何执行的结果都是相同的,就好像所有处理器的操作都是按某种顺序执行的,每个处理器的操作都是按程序指定的顺序出现的。满足这一条件的多处理器系统将被称为顺序一致的。

今天,我们不仅讨论计算机硬件,还讨论保证顺序一致性的编程语言,当程序的唯一可能执行对应于某种线程操作交错成顺序执行时。顺序一致性通常被认为是理想的模型,是程序员最自然的工作模式。它允许您假设程序按照它们在页面上出现的顺序执行,并且单个线程的执行只是以某种顺序交错(interleaving),而不是以其他方式排列。

人们可能会有理由质疑顺序一致性是否应该是理想的模型,但这超出了本文的范围。我只注意到,考虑到所有可能的线程交错(interleaving)依然存在,就像在1979年一样,即使过了四十几年,Leslie Lamport的“设计和证明多处理算法正确性的惯用方法”,依然没有什么能取代它。

之前我问这个程序能不能打印0:

// Thread 1           // Thread 2
x = 1;                while(done == 0) { /* loop */ }
done = 1;             print(x);

为了让程序更容易分析,让我们去掉循环和打印,并询问读取共享变量的可能结果:

Litmus Test: Message Passing
Can this program see r1 = 1, r2 = 0?
// Thread 1           // Thread 2
x = 1                 r1 = y
y = 1                 r2 = x

我们假设每个例子都是所有共享变量最初都被设置为零。因为我们试图确定硬件允许做什么,我们假设每个线程都在自己的专用处理器上执行,并且没有编译器来对线程中发生的事情进行重新排序:列表中的指令就是处理器执行的指令。rN这个名字表示一个线程本地寄存器,而不是一个共享变量,我们会问一个线程本地寄存器的值在执行结束时是否存在某种可能。

这种关于样本程序执行结果的问题被称为litmus test。因为它只有两个答案——这个结果可能还是不可能?——litmus test为我们提供了一种区分内存模型的清晰方法:如果一个模型支持特定的执行,而另一个不支持,那么这两个模型显然是不同的。不幸的是,正如我们将在后面看到的,一个特定的模型对一个特定的litmus test给出的答案往往令人惊讶。

If the execution

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值