下载本文的PDF版本 PDF

软件和并发革命

要充分利用多核处理器的强大功能,软件行业需要新的工具和新的思维方式。

HERB SUTTER 和 JAMES LARUS,微软

长期以来,并发一直被吹捧为“下一个重大事件”和“未来的发展方向”,但在过去的30年里,主流软件开发一直能够忽略它。我们的并行未来终于到来了:新的机器将是并行机器,这将需要我们开发软件的方式发生重大变化。

本期杂志的介绍性文章(Kunle Olukotun 和 Lance Hammond 的“微处理器的未来”)描述了计算机体系结构从单处理器转向多核处理器(也称为 CMP(芯片多处理器))的硬件必然性。(有关相关分析,请参阅“免费午餐结束了:软件中向并发性的根本转变”。1)

在本文中,我们重点关注并发对软件的影响及其对编程语言和程序员的后果。

Olukotun 和 Hammond 描述的硬件变化代表了计算领域的根本转变。在过去的三十年里,半导体制造和处理器实现的改进稳步提高了计算机执行现有顺序程序的速度。多核处理器的架构变化仅有利于并发应用程序,因此对于大多数现有的主流软件来说几乎没有价值。在可预见的未来,今天的桌面应用程序的运行速度不会比现在快多少。事实上,它们在新芯片上的运行速度可能会略微降低,因为单个内核变得更简单,并在较低的时钟速度下运行,以降低密集多核处理器上的功耗。

这给我们带来了软件开发(至少对于主流软件而言)的一个根本转折点。计算机将继续变得越来越强大,但除非程序是高度并发的,否则程序不能再简单地搭上硬件性能提升的顺风车。

虽然多核性能是强制性因素,但我们还有其他理由想要并发:特别是,通过异步而不是同步执行工作来提高响应能力。例如,今天的应用程序必须将工作移出 GUI 线程,以便在后台运行计算时可以重绘屏幕。

但是并发是困难的。不仅今天的语言和工具不足以将应用程序转换为并行程序,而且在主流应用程序中找到并行性也很困难,而且——最糟糕的是——并发要求程序员以人类觉得困难的方式思考。

然而,多核机器是未来,我们必须弄清楚如何对其进行编程。本文的其余部分深入探讨了为什么它很困难的一些原因,以及一些可能的解决方案方向。

后果:软件的新时代

今天的并发编程语言和工具的水平与结构化编程时代初期的顺序编程相当。信号量和协程是并发的汇编程序,而锁和线程是稍微高级的结构化并发构造。我们需要的是面向对象的并发——更高级别的抽象,以帮助构建并发程序,就像面向对象的抽象帮助构建大型组件化程序一样。

由于几个原因,并发革命可能比面向对象革命更具颠覆性。首先,并发将是提高性能不可或缺的一部分。像 C 这样的语言忽略了面向对象,并且对于许多程序仍然可用。如果并发成为提高硬件性能的唯一途径,那么商业和系统编程语言的价值将取决于它们对并发编程的支持。现有的语言(如 C)将获得超出简单模型(如 pthreads)的并发功能。未能支持并发编程的语言将逐渐消亡,并且只有在现代硬件无关紧要时才有用。

并发比面向对象更具颠覆性的第二个原因是,虽然顺序编程很困难,但并发编程显然更困难。例如,顺序程序的上下文敏感分析是一种在分析程序时考虑调用上下文的基本技术。并发程序也需要同步分析,但同时执行这两种分析被证明是不可判定的。2

最后,人类很快就会被并发所淹没,并且发现推理并发代码比顺序代码困难得多。即使是细心的人也会错过简单部分有序操作集合中可能的交错。

客户端和服务器应用程序之间的差异

并发对于客户端应用程序来说是一个具有挑战性的问题。然而,对于许多基于服务器的程序来说,并发是一个“已解决的问题”,因为我们通常会设计出运行良好的并发解决方案,尽管对它们进行编程并确保它们能够扩展仍然需要付出巨大的努力。这些应用程序通常具有丰富的并行性,因为它们同时处理许多独立的请求流。例如,Web 服务器或网站独立地在主要不重叠的数据上执行同一代码的数千个副本。

此外,这些执行被很好地隔离,并通过抽象数据存储(例如支持对结构化数据进行高度并发访问的数据库)共享状态。最终效果是,通过数据库共享数据的代码可以保持其“平静轻松的感觉”——生活在一个整洁的、单线程宇宙中的错觉。

客户端应用程序的世界远没有那么结构化和规律。典型的客户端应用程序代表单个用户执行相对较小的计算,因此通过将计算划分为更小的部分来找到并发性。这些部分(例如用户界面和程序的计算)以无数种方式交互和共享数据。使这种类型的程序难以并发执行的是非同构代码;细粒度、复杂的交互;以及基于指针的数据结构。

编程模型

今天,您可以通过多种不同的方式表达并行性,每种方式仅适用于程序的一个子集。在许多情况下,如果不进行仔细的设计和分析,很难预先知道哪种模型适合特定问题,并且当给定应用程序不能完全适合单个范例时,始终很难组合多个模型。

这些并行编程模型在两个维度上显着不同:并行操作的粒度和这些任务之间的耦合程度。这个空间中的不同点偏爱不同的编程模型,因此让我们依次检查这些轴。

并行执行的操作范围可以从单个指令(例如加法或乘法)到需要数小时或数天才能运行的复杂程序。显然,对于小型操作,并行基础设施的开销成本很高;例如,并行指令执行通常需要硬件支持。与传统的微处理器相比,多核处理器降低了通信和同步成本,这可以减轻较小代码片段的开销负担。尽管如此,总的来说,任务的粒度越细,就越需要关注将其作为单独任务生成以及提供其通信和同步的成本。

另一个维度是操作之间通信和同步的耦合程度。理想情况是没有:操作完全独立运行并产生不同的输出。在这种情况下,操作可以按任何顺序运行,不会产生同步或通信成本,并且易于编程,而不会出现数据竞争的可能性。这种情况很少见,因为大多数并发程序都在其操作之间共享数据。随着操作变得更加多样化,确保正确高效操作的复杂性也会增加。最简单的情况是为每个操作执行相同的代码。这种类型的共享通常是规则的,并且可以通过仅分析单个任务来理解。更具挑战性的是不规则并行性,其中操作是不同的,并且共享模式更难以理解。

独立并行

也许最简单和表现最佳的模型是独立并行(有时称为“令人尴尬的并行任务”),其中一个或多个操作独立应用于数据集合中的每个项目。

细粒度数据并行性依赖于并发执行的操作的独立性。它们不应共享输入数据或结果,并且应在无需协调的情况下执行。例如

double A[100][100];

A = A * 2;

将 100x100 数组的每个元素乘以 2,并将结果存储在相同的数组位置。10,000 次乘法中的每一次都独立进行,无需与其对等方协调。对于大多数计算机来说,这可能比必要的并发性更多,并且其粒度非常细,因此更实用的方法是将矩阵划分为 n x m 块,并并发执行对块的操作。

在粒度轴的另一端,某些应用程序(例如搜索引擎)仅共享一个大型只读数据库,因此并发处理查询不需要协调。类似地,大型模拟(需要多次运行才能探索大量的输入参数空间)是另一种令人尴尬的并行应用程序。

规则并行

独立并行之后的下一步是将相同的操作应用于数据集合,当计算相互依赖时。如果两个操作之间存在通信或同步,则对一块数据的操作依赖于另一个操作。

例如,考虑一个模板计算,该计算将数组中的每个点替换为其四个最近邻居的平均值

A[i, j] = (A[i-1, j] + A[i, j-1] + A[i+1, j] + A[i, j+1]) / 4;

此计算需要仔细协调,以确保数组位置在其平均值替换之前被其邻居读取。如果空间不是问题,则可以将平均值计算到新数组中。通常,其他更结构化的计算策略(例如以对角线波前遍历数组)将产生相同的结果,并具有更好的缓存局部性和更低的内存消耗。

规则并行程序可能需要同步或精心策划的执行策略来产生正确的结果,但与一般并行性不同,可以分析操作背后的代码,以确定如何并发执行它们以及它们共享哪些数据。这种优势有时是假设性的,因为程序分析是一门不精确的学科,并且编译器无法理解和重构足够复杂的程序。

在粒度轴的另一端,网站上的计算通常是独立的,除了对公共数据库的访问。计算并行运行,除了数据库事务之外,没有大量的协调。这确保了对同一数据的并发访问得到一致的解决。

非结构化并行

最通用且最不受约束的并行形式是当并发计算不同时,因此它们的数据访问是不可预测的,并且需要通过显式同步来协调。这是使用线程和显式同步编写的程序中最常见的并行形式,其中每个线程在程序中都有不同的角色。一般来说,除了两个线程中冲突的数据访问需要显式同步之外,关于这种形式的并行性没有什么具体可说的;否则,程序将是不确定的。

共享状态问题,以及为什么锁不是答案

非结构化并行的另一个具有挑战性的方面是共享非结构化状态。客户端应用程序通常操作组织为不可预测的互连对象图的共享内存。

当两个任务尝试访问同一对象,并且其中一个任务可能会修改其状态时,如果我们不采取任何措施来协调任务,则会发生数据竞争。竞争是不好的,因为并发任务可以读取和写入不一致或损坏的值。

有各种各样的同步设备可以防止竞争。其中最简单的是锁。每个想要访问一块共享数据的任务都必须获取该数据的锁,执行其计算,然后释放锁,以便可以继续对数据进行其他操作。不幸的是,尽管锁有效,但它们给现代软件开发带来了严重的问题。

锁的一个根本问题是它们不可组合。您不能采用两个正确的基于锁的代码片段,将它们组合起来,并知道结果仍然正确。现代软件开发依赖于将库组合成更大程序的能力,因此我们无法在不检查其实现的情况下构建基于锁的组件,这是一个严重的难题。

可组合性问题主要源于死锁的可能性。在其最简单的形式中,当两个锁可能被两个任务以相反的顺序获取时,就会发生死锁:任务 T1 获取锁 L1,任务 T2 获取锁 L2,然后 T1 尝试获取 L2,而 T2 尝试获取 L1。两者都永远阻塞。因为每当可以以相反的顺序获取两个锁时,就会发生这种情况,所以在持有锁时调用您不控制的代码是导致死锁的根本原因。

然而,这正是可扩展框架所做的,因为它们在持有锁时调用虚函数。今天最好的商业应用程序框架都这样做,包括 .NET Frameworks 和 Java 标准库。我们侥幸逃脱了,因为开发人员还没有编写大量频繁锁定的重度并发程序。许多复杂的模型试图解决死锁问题——例如,使用退避和重试协议——但它们需要程序员的严格纪律,并且有些模型会引入自身的问题(例如,活锁)。

通过保证锁将始终以安全顺序获取来避免死锁的技术也无法组合。例如,锁分级和锁层次结构通过要求在给定级别上的所有锁一次以预定顺序获取,并且在持有某个级别的锁时,您只能获取更高级别的附加锁,从而防止程序以冲突的顺序获取锁。这些技术在一个团队维护的模块或框架内有效(尽管它们在实践中未得到充分利用),但它们假设控制整个代码库。这严重限制了它们在可扩展框架、插件系统和其他将不同方编写的代码组合在一起的情况下的使用。

锁更基本的问题是它们依赖于程序员严格遵守约定。锁与其保护的数据之间的关系是隐式的,并且仅通过程序员的纪律来维护。程序员必须始终记住在接触共享数据之前在正确的点获取正确的锁。程序中控制锁的约定有时会被写下来,但它们几乎从未被精确地陈述到工具可以检查它们的程度。

锁还有其他更微妙的问题。锁定是一个全局程序属性,很难将其本地化到单个过程、类或框架。所有访问一块共享状态的代码都必须知道并遵守锁定约定,无论谁编写了代码或代码位于何处。

使同步成为局部属性的尝试并非总是有效。考虑一种流行的解决方案,例如 Java 的 synchronized 方法。对象的每个方法都可以获取对象上的锁,因此没有两个线程可以同时直接操作对象的状态。只要对象的状态仅通过其方法访问,并且程序员记住添加 synchronized 声明,此方法就有效。

synchronized 方法至少存在三个主要问题。首先,它们不适用于其方法调用其他对象上的虚函数的类型(例如,Java 的 Vector 和 .NET 的 SyncHashTable),因为在持有锁时调用第三方代码会打开死锁的可能性。其次,synchronized 方法可能会执行过多的锁定,通过获取和释放所有对象实例上的锁,即使是那些从未跨线程共享的实例(通常是大多数)。第三,synchronized 方法也可能执行过少的锁定,当程序在对象或不同对象上调用多个方法时,无法保持原子性。作为后者的一个简单示例,请考虑银行转账

account1.Credit(amount); account2.Debit(amount)

每个对象的锁定都会保护每次调用,但不能阻止另一个线程在调用之间看到两个帐户的不一致状态。这种类型的操作,其原子性与方法调用边界不对应,需要额外的显式同步。

锁的替代方案

为了完整性,我们注意到锁的两个主要替代方案。第一个是无锁编程。通过深入了解处理器的内存模型,可以创建无需显式锁定即可共享的数据结构。无锁编程既困难又脆弱;发明一种新的无锁数据结构实现仍然通常是可以发表的结果。

第二个替代方案是事务内存,它将数据库事务的核心思想带入编程语言中。程序员将其程序编写为一系列显式原子块,这些原子块看起来是不可分割地执行的,因此并发执行的操作严格在原子操作执行之前或之后看到共享状态。尽管许多人将事务内存视为一个有希望的方向,但它仍然是积极研究的主题。

我们在编程语言中需要什么

我们需要更高级别的语言抽象,包括对当前命令式语言的进化扩展,以便现有应用程序可以逐步变为并发的。编程模型必须使并发易于理解和推理,不仅在初始开发期间,而且在维护期间也是如此。

显式、隐式和自动并行化

显式编程模型提供抽象,要求程序员准确说明并发可能发生的位置。显式表达并发的主要优点是它允许程序员充分利用其应用程序领域知识,并表达应用程序中的全部潜在并发性。然而,它也有缺点。它需要新的更高级别的编程抽象以及在存在共享数据的情况下更高的程序员熟练程度。

隐式编程模型将并发隐藏在库中或 API 之后,以便调用者保留顺序世界观,而库并行执行工作。这种方法使幼稚的程序员可以安全地使用并发。它的主要缺点是某些类型的与并发相关的性能提升无法通过这种方式实现。此外,很难设计在任何情况下都不会暴露并发性的接口——例如,当程序将操作应用于同一数据的多个实例时。

另一种广泛研究的方法是自动并行化,编译器尝试在并行化中找到并行性,通常在用 Fortran 等传统语言编写的程序中。尽管它看起来很吸引人,但这种方法在实践中效果不佳。准确的程序分析对于理解程序的潜在行为是必要的。对于 Fortran 等简单语言,这种分析具有挑战性,而对于 C 等操作基于指针的数据的语言,这种分析则更加困难。此外,顺序程序通常使用顺序算法,并且包含很少的并发性。

命令式语言和函数式语言。

流行的商业编程语言(例如,Pascal、C、C++、Java、C#)是命令式语言,其中程序员指定对变量和数据结构的逐步更改。细粒度控制结构(例如,for 循环)、低级数据操作和共享可变对象实例使得这些语言中的程序难以分析和自动并行化。

普遍的看法是,函数式语言(例如,Scheme、ML 或 Haskell)可以消除这种困难,因为它们自然适合并发。用这些语言编写的程序操作不可变的对象实例,这些实例不会造成并发危害。此外,在没有副作用的情况下,程序似乎对执行顺序的约束更少。

然而,在实践中,函数式语言不一定有利于并发。函数式程序中暴露的并行性通常在过程调用级别,这对于传统的并行处理器来说粒度太细。此外,大多数函数式语言都允许对可变状态进行一些副作用,并且使用这些功能的代码很难自动并行化。

这些语言为了表达性和效率而重新引入了可变状态。在纯函数式语言中,聚合数据结构(例如,数组或树)通过生成包含修改值的副本来更新。这种技术在语义上很有吸引力,但对于性能来说可能是可怕的(线性算法很容易变成二次算法)。此外,函数式更新无助于阻止编写严格的顺序算法,其中每个操作都等待直到前一个操作更新程序的状态。

函数式语言对并发的真正贡献在于这些语言中常用的更高级别的编程风格,其中诸如 map 或 map-reduce 之类的操作将计算应用于聚合数据结构的所有元素。这些更高级别的操作是并发性的丰富来源。幸运的是,这种编程风格并非天生就与函数式语言联系在一起,但在命令式程序中也很有价值。

例如,Google 研究员 Jeffrey Dean 和 Sanjay Ghemawat 描述了 Google 如何使用 Map-Reduce 来表达大规模分布式计算。3 命令式语言可以明智地添加函数式风格的扩展,从而从这些功能中受益。这很重要,因为行业不能仅仅从头开始。为了保留对当前世界软件的巨额投资,必须逐步添加对并发性的支持,同时保留软件开发人员在命令式语言方面的专业知识和培训。

更好的抽象

今天的大多数语言都提供线程和锁级别的显式编程。这些抽象是低级别的,并且难以系统地推理。由于这些构造是构建抽象的基础薄弱,因此它们鼓励使用多线程编程,并带来任意阻塞和重入问题。

更高级别的抽象允许程序员表达具有内在并发性的任务,运行时系统可以组合和调度这些任务以适应实际机器上的硬件。这将使应用程序在新硬件上表现更好。此外,对于主流开发,程序员将重视任务内顺序执行的错觉。

更高级别抽象的两个基本示例是异步调用和 Future。异步调用是函数或方法的非阻塞调用。调用者继续执行,并且从概念上讲,消息被发送到任务或 fork 以独立执行操作。Future 是一种从异步调用返回结果的机制;它是尚未物化的值的占位符。

更高级别抽象的另一个示例是活动对象,它在概念上在其自己的线程上运行,因此创建 1,000 个这样的对象在概念上创建了 1,000 个潜在的执行线程。活动对象的行为类似于监视器,因为在给定时间只有一个对象的方法执行,但它不需要传统的锁定。相反,来自活动对象外部的方法调用是异步消息,由对象编组、排队和泵送。活动对象有许多设计,从专门的 actor 语言到可从传统 C 代码调用的 COM 单线程单元,以及许多设计变量。

还需要其他更高级别的抽象,例如描述和检查异步消息交换的协议。它们应该共同构成一个一致的编程模型,该模型可以表达跨所有主要粒度级别的典型应用程序并发性需求。

我们在工具中需要什么

并行编程由于其陌生性和内在难度,将需要更好的编程工具来系统地查找缺陷、帮助调试程序、查找性能瓶颈以及帮助测试。如果没有这些工具,并发将成为阻碍,降低开发人员和测试人员的生产力,并使并发软件更昂贵且质量更低。

并发引入了新型的编程错误,超出了顺序代码中那些过于熟悉的错误。数据竞争(由于同步不足和死锁导致)和活锁(由于不正确的同步导致)是难以发现和理解的缺陷,因为它们的行为通常是不确定的并且难以重现。传统的调试方法(例如,通过在程序执行中较早的位置设置断点来重新执行程序)对于并发程序效果不佳,因为并发程序的执行路径和行为可能会因一次执行而异。

系统的缺陷检测工具在这个世界中非常有价值。这些工具使用静态程序分析来系统地探索程序的所有可能的执行;因此,它们可以捕获无法重现的错误。尽管类似的技术(例如模型检查)已成功用于查找硬件中的缺陷(硬件本质上是并发的),但软件更困难。典型程序的状态空间远大于大多数硬件的状态空间,因此系统地探索工件状态的技术还有更多工作要做。在这两种情况下,模块化和抽象都是使分析易于处理的关键。在硬件模型测试中,如果您可以分离出 ALU(算术逻辑单元)并独立于寄存器文件对其进行分析,那么您的任务就会变得容易得多。

这给我们带来了软件更难分析的第二个原因:将程序的各个部分切分出来,单独分析它们,然后组合结果以查看它们如何协同工作要困难得多。共享状态、未指定的接口和未记录的交互使这项任务对软件来说更具挑战性。

并发软件的缺陷检测工具是活跃的研究领域。微软研究院的一项名为 KISS(保持严格顺序)的有希望的技术4 将线程程序转换为顺序程序,其执行行为包括原始线程的所有可能的交错,这些交错最多涉及两次上下文切换。然后可以通过大量现有的顺序工具分析转换后的程序,这些工具随后成为此有界模型的并发缺陷检测工具。

即使有这样的进步,程序员仍然需要好的调试器,让他们能够理解并行程序中复杂且难以重现的交互。有两种通用技术可以收集此信息。第一种是更好的日志记录工具,它可以跟踪哪些消息发送到哪个进程或哪个线程访问了哪个对象,以便开发人员可以回顾并了解程序的局部有序执行。开发人员还将希望能够跟踪跨线程的因果关系(例如,发送到一个活动对象的消息,在执行时,导致发送到其他活动对象的哪些其他消息?),重放和重新排序队列中的消息,单步执行包括回调的异步调用模式,以及以其他方式检查其代码的并发执行。第二种方法是反向执行,它允许程序员在程序的执行历史中备份并重新执行一些代码。重放调试是一个古老的想法,但其成本和复杂性一直是采用的障碍。最近,虚拟机监视器降低了这两个因素。5 在并发世界中,这项技术很可能成为必需品。

性能调试和调优也需要在并发世界中使用新工具。并发引入了新的性能瓶颈,例如锁争用、缓存一致性开销和锁队列,这些瓶颈通常难以通过简单的性能分析器来识别。更了解底层计算机体系结构和程序并发结构的新工具将能够更好地识别这些问题。

测试也必须改变。并发程序由于其不确定的行为,因此更难以测试。简单的代码覆盖率指标(跟踪语句或分支是否已执行)需要扩展以考虑与其他并发执行的代码,否则测试将提供程序已完全执行的过于乐观的图景。此外,简单的压力测试将需要通过更系统化的技术来增强,这些技术使用类似模型检查的技术来探索系统的状态空间。例如,Verisoft 已非常成功地使用这些技术来查找并发电话交换软件中的错误。6 今天,许多并发应用程序使用压力测试的长度来获得对应用程序不太可能包含严重竞争的信心。未来,这将越来越不足,软件开发人员将需要能够通过严格的确定性测试来证明其产品的质量,而不是依赖于基于压力测试的概率性信心。

并行性是关键

并发革命主要是一场软件革命。困难的问题不是构建多核硬件,而是以一种使主流应用程序能够从 CPU 性能持续指数增长中受益的方式对其进行编程。

软件行业需要回到现有应用程序在新硬件上运行速度更快的状态。为此,我们必须开始编写并发应用程序,其中包含至少几十个,最好是数百个可分离的任务(并非所有任务都需要在给定点处于活动状态)。

并发还开启了新的、更丰富的计算机界面以及更强大和功能更强大的软件的可能性。这需要新的想象力爆发,以寻找和利用新处理器指数增长的潜力的新用途。

为了实现此类应用程序,编程语言设计者、系统构建者和编程工具创建者需要开始认真考虑并行性,并找到比线程和显式同步的低级工具更好的技术,这些低级工具是当今并行程序的基本构建块。我们需要更高级别的并行构造,这些构造可以更清晰地表达程序员的意图,以便程序的并行架构更加可见、易于理解,并且可以被工具验证。

参考文献

1. Sutter, H. 2005. 免费午餐结束了:软件中向并发性的根本转变。 Dr. Dobb’s Journal 30 (3); http://www.gotw.ca/publications/concurrency-ddj.htm.

2. Ramalingam, G. 2000. 上下文敏感的同步敏感分析是不可判定的。 Transactions on Programming Languages and Systems 22 (2): 416-430.

3. Dean, J., 和 Ghemawat, S. 2004. MapReduce:大型集群上的简化数据处理。第六届操作系统设计与实现研讨会论文集,旧金山,加利福尼亚州:137-150。

4. Qadeer, S., 和 Wu, D. 2004. KISS:保持简单和顺序。 SIGPLAN 2004 编程语言设计与实现会议论文集,华盛顿特区:1-13。

5. King, S. T., Dunlap, G. W., 和 Chen, P. M. 2005. 使用时间旅行虚拟机调试操作系统。 2005 年 Usenix 年度技术会议论文集,阿纳海姆,加利福尼亚州:1-15。

6. Chandra, S., Godefroid, P., 和 Palm, C. 2002. 实践中的软件模型检查:工业案例研究。第 24 届国际软件工程会议论文集,奥兰多,佛罗里达州:431-441。

HERB SUTTER 是微软开发部门的软件架构师。他担任 ISO C++ 标准委员会主席,并且是四本书和 200 多篇技术论文和文章的作者,包括广为流传的关于并发革命的文章“免费午餐结束了”。可以通过 [email protected] 与他联系。

詹姆斯·拉鲁斯是微软研究院的高级研究员,管理着SWIG(软件改进组),该组由SPT(软件生产力工具)、TVM(测试、验证和测量)和HIP(人机交互编程)研究小组组成,并负责运行Singularity研究项目。在加入微软之前,他曾担任威斯康星大学麦迪逊分校的副教授,在那里他共同领导了威斯康星风洞研究项目。这个由DARPA和NSF资助的项目旨在研究构建和编程并行共享内存计算机的新方法。拉鲁斯在加州大学伯克利分校获得了计算机科学博士学位。

acmqueue

最初发表于Queue杂志第3卷第7期
数字图书馆中评论这篇文章





更多相关文章

亚当·莫里森 - 多核程序中的同步扩展
为现代多核处理器设计软件带来了一个难题。传统的软件设计中,线程操作共享数据,其可扩展性有限,因为对共享数据更新的同步会串行化线程并限制并行性。另一种分布式软件设计,其中线程不共享可变数据,消除了同步并提供了更好的可扩展性。但是,分布式设计使得实现共享数据结构自然提供的功能(例如动态负载均衡和强一致性保证)变得具有挑战性,并且并非适用于所有程序。然而,通常情况下,共享可变数据结构的性能受到当前使用的同步方法(无论是基于锁还是无锁)的限制。


法比安·高德,巴蒂斯特·勒佩尔,贾斯汀·芬斯顿,穆罕默德·达什蒂,亚历山德拉·费多罗娃,维维安·奎马,雷诺·拉谢兹,马克·罗斯 - 现代NUMA系统上内存管理的挑战
现代服务器级系统通常由多个多核芯片组合在一个系统中构建而成。每个芯片都有一个本地DRAM(动态随机存取存储器)模块;它们一起被称为一个节点。节点通过高速互连连接,系统是完全一致的。这意味着,对程序员透明的是,一个核心可以向其节点本地内存以及其他节点的内存发出请求。关键的区别在于远程请求将花费更长的时间,因为它们会受到更长的线路延迟的影响,并且可能在遍历互连时必须跳过多个跃点。


斯宾塞·拉斯本 - 使用Promise进行并行处理
在当今世界,有很多理由编写并发软件。提高性能和吞吐量的愿望导致了许多不同的异步技术。然而,所涉及的技术通常很复杂,并且是许多细微错误的根源,特别是当它们需要共享可变状态时。如果不需要共享状态,那么这些问题可以通过一种称为Promise的更好抽象来解决。这些Promise允许程序员将异步函数调用连接在一起,等待每个调用返回成功或失败,然后再运行链中的下一个适当的函数。


戴维洛尔·布索 - 实用同步原语的可扩展性技术
在理想的世界中,应用程序在越来越大的系统上执行时,应该能够自动扩展。然而,在实践中,不仅这种扩展没有发生,而且在更大的系统上,性能实际上会变得更糟,这种情况也很常见。





© 公司。版权所有。

© . All rights reserved.