acm-headergydF4y2Ba
登录gydF4y2Ba

ACM通信gydF4y2Ba

研究突出了gydF4y2Ba

可组合内存事务gydF4y2Ba


编写并发程序是出了名的困难,并且越来越具有实际意义。一个特别值得关注的问题是,即使正确实现的并发抽象也不能组合在一起形成更大的抽象。在本文中,我们提出了一个基于gydF4y2Ba事务内存,gydF4y2Ba这提供了更丰富的构成。事务性内存的所有通常的好处都在这里(例如,不受低级死锁的影响),但是除此之外,我们还描述了gydF4y2Ba阻塞gydF4y2Ba而且gydF4y2Ba选择gydF4y2Ba这在早期的研究中是无法达到的。gydF4y2Ba

回到顶部gydF4y2Ba

1.简介gydF4y2Ba

免费午餐结束了。gydF4y2Ba25gydF4y2Ba我们已经习惯了这样的想法,即当我们购买下一代处理器时,我们的程序会运行得更快,但这种时间已经过去了。虽然下一代芯片将拥有更多的CPU,但每个CPU都不会比前一年的型号更快。如果我们想让程序运行得更快,就必须学会编写并行程序。gydF4y2Ba

编写并行程序是出了名的棘手。主流的基于锁的抽象很难使用,它们使得设计可靠和可伸缩的计算机系统变得困难。此外,使用锁构建的系统在不了解其内部结构的情况下很难组成。gydF4y2Ba

为了解决其中一些困难,一些研究人员(包括我们自己)建议在上面构建编程语言特性gydF4y2Ba软件事务性内存gydF4y2Ba(STM),它可以原子地执行一组内存操作。gydF4y2Ba23gydF4y2Ba使用事务内存而不是锁带来了众所周知的优点:不受死锁和优先级反转的影响,不受异常或超时的自动回滚的影响,不受锁粒度和并发性之间的紧张关系的影响。gydF4y2Ba

早期的软件事务存储工作存在一些缺点。首先,它没有阻止事务代码绕过STM接口,在事务中访问数据的同时直接访问数据。这种冲突可能无法检测到,并阻止事务原子地执行。此外,早期的STM系统没有为构建可能阻塞的操作提供令人信服的描述,例如,一个支持在队列变为空时等待的操作的共享工作队列。gydF4y2Ba

我们在STM-Haskell上的工作旨在解决这些问题。具体而言,我们的原创论文做出了以下贡献:gydF4y2Ba

  • 我们在纯函数式语言Haskell(第3节)的背景下重新表达了事务性内存的思想。正如我们所展示的,STM可以用声明式语言特别优雅地表达,并且我们能够使用Haskell的类型系统提供比传统可能提供的强大得多的保证。特别地,我们保证“强原子性”gydF4y2Ba15gydF4y2Ba在这种情况下,事务似乎总是原子地执行,而不管程序的其余部分在做什么。此外,事务是组合的:小事务可以结合在一起形成较大的事务。gydF4y2Ba
  • 我们提出了一种模块化的阻塞形式(第3.2节)。这个想法很简单:一个事务调用一个gydF4y2Ba重试gydF4y2Ba操作,以表明它尚未准备好运行(例如,它正试图从空队列中获取数据)。程序员不需要确定启用它的条件;STM会自动检测到这一点。gydF4y2Ba
  • 的gydF4y2Ba重试gydF4y2Ba函数允许在其中组合可能阻塞的事务gydF4y2Ba序列。gydF4y2Ba除此之外,我们还提供gydF4y2BaorElsegydF4y2Ba,这使得它们可以组成gydF4y2Ba选择,gydF4y2Ba因此,如果第一个程序重试,则运行第二个程序(参见第3.4节)。这种能力允许线程同时等待许多事情,就像UnixgydF4y2Ba选择gydF4y2Ba系统callexceptgydF4y2BaorElsegydF4y2Ba组成,而gydF4y2Ba选择gydF4y2Ba没有。gydF4y2Ba

我们所描述的一切都是在格拉斯哥Haskell编译器(GHC)中完全实现的,GHC是一个针对并发Haskell的成熟优化编译器;STM增强在2005年的GHC 6.4版本中被合并。还提供了更多的示例和面向程序员的教程。gydF4y2Ba19gydF4y2Ba

我们主要的战争口号是gydF4y2Ba组合性:gydF4y2Ba程序员可以以尊重抽象障碍的模块化方式控制原子性和阻塞行为。相比之下,基于锁的方法会导致抽象和并发性之间的直接冲突(参见第2节)。综合起来,这些思想在对模块化并发性的语言支持方面提供了质的改进,类似于从汇编代码迁移到高级语言的改进。就像汇编代码一样,有足够时间和技能的程序员可以直接使用低级并发控制机制而不是事务来获得更好的编程性能。但是对于除了要求最高的应用程序之外的所有应用程序,我们的高级STM抽象已经足够好了。gydF4y2Ba

这篇论文是一篇标题相同的早期论文的缩写和润色版。gydF4y2Ba9gydF4y2Ba从那时起,关于事务性内存的各个方面有了大量的研究,但几乎所有的研究都是关于gydF4y2Ba原子记忆更新,gydF4y2Ba而很少关注我们的中心问题gydF4y2Ba阻塞gydF4y2Ba而且gydF4y2Ba同步gydF4y2Ba线程之间,例如gydF4y2Ba重试gydF4y2Ba而且gydF4y2BaorElsegydF4y2Ba.在我们看来,这是一个严重的疏忽:没有条件变量的锁的作用有限。gydF4y2Ba

事务内存具有复杂的语义,最初的论文为事务提供了精确的、正式的语义,并描述了我们的实现。由于篇幅限制,这里省略了这两个部分。gydF4y2Ba

回到顶部gydF4y2Ba

2.背景gydF4y2Ba

在本文中,我们研究了在共享内存机器上运行的线程之间的并发性;我们不考虑通过存储系统或数据库进行外部交互的问题,也不讨论分布式系统。我们考虑的问题类型是构建集合类(队列、列表等)和并发线程用来维护共享信息的其他数据结构。还有许多我们没有讨论的其他并发性方法,包括来自像NESL这样的语言的数据并行抽象gydF4y2Ba2gydF4y2Ba以及来自高性能计算社区的软件,如OpenMP和MPI。gydF4y2Ba

即使在这种受限的设置中,并发编程也是极其困难的。主流的编程技术是基于gydF4y2Ba锁,gydF4y2Ba这是一种简单直接的方法,但不能随程序规模和复杂性而扩展。以确保gydF4y2Ba正确性,gydF4y2Ba程序员必须确定哪些操作发生冲突;以确保gydF4y2Ba活性,gydF4y2Ba他们必须避免引入僵局;保证良好的gydF4y2Ba的性能,gydF4y2Ba他们必须在执行锁定的粒度与细粒度锁定的成本之间进行平衡。gydF4y2Ba

然而,也许最根本的反对意见是这个gydF4y2Ba基于锁的程序不会合成。gydF4y2Ba例如,考虑一个具有线程安全的插入和删除操作的哈希表。现在假设我们想要删除一项gydF4y2Ba一个gydF4y2Ba从表gydF4y2Bat1gydF4y2Ba,插入到表中gydF4y2Bat2gydF4y2Ba;但是中间状态(其中两个表都不包含项)必须对其他线程不可见。除非哈希表的实现者预期到这种需求,否则根本没有办法在不以某种方式锁定的情况下满足这种需求gydF4y2Ba所有gydF4y2Ba对表的其他访问。一种方法是公开并发控制方法,例如gydF4y2BaLockTablegydF4y2Ba而且gydF4y2BaUnlockTablegydF4y2Ba但是这破坏了哈希表的抽象,并导致锁引起的死锁,这取决于客户端获取锁的顺序,或者如果客户端忘记了就会出现竞争条件。然而,如果客户端想要等待的存在,则需要更多的复杂性gydF4y2Ba一个gydF4y2Ba在gydF4y2Bat1gydF4y2Ba但是这种阻塞行为不能锁定表(否则gydF4y2Ba一个gydF4y2Ba不能插入)。简而言之,单个正确的操作(插入、删除)不能组合成更大的正确操作。gydF4y2Ba

同样的现象出现在尝试组合替代阻塞操作时。假设一个程序gydF4y2Bap1gydF4y2Ba使用对Unix的调用,等待两个输入管道中的一个有数据gydF4y2Ba选择gydF4y2Ba程序;假设另一个过程gydF4y2Bap2gydF4y2Ba在另外两个管道上执行相同的操作。在Unix中,没有办法执行gydF4y2Ba选择gydF4y2Ba之间的gydF4y2Bap1gydF4y2Ba而且gydF4y2Bap2gydF4y2Ba,构成性的根本丧失。相反,Unix程序员学习笨拙的编程技术来收集所有必须等待的文件描述符,执行单个顶层gydF4y2Ba选择gydF4y2Ba,然后分派回正确的处理人员。两个单独正确的抽象概念,gydF4y2Bap1gydF4y2Ba而且gydF4y2Bap2gydF4y2Ba,不能组合成一个更大的;相反,它们必须被分割和笨拙地合并,这与抽象的目标直接冲突。gydF4y2Ba

与固定锁相比,更有前途和更激进的替代方案是将并发控制建立在gydF4y2Ba原子内存事务,gydF4y2Ba也被称为gydF4y2Ba事务内存。gydF4y2Ba我们将展示事务内存为并发性和抽象之间的矛盾提供了一种解决方案。例如,对于内存事务,我们可以这样操作哈希表:gydF4y2Ba

ins01.gifgydF4y2Ba

等待这两者中的任何一个gydF4y2Bap1gydF4y2Ba或gydF4y2Bap2gydF4y2Ba我们可以说gydF4y2Ba

ins02.gifgydF4y2Ba

的实现不需要了解这些简单的结构gydF4y2Ba插入gydF4y2Ba,gydF4y2Ba删除gydF4y2Ba,gydF4y2Bap1gydF4y2Ba或gydF4y2Bap2gydF4y2Ba,如果这些操作可能阻塞,它们将继续正常工作,正如我们将看到的。gydF4y2Ba

*gydF4y2Ba2.1.事务内存gydF4y2Ba

事务的概念并不新鲜。多年来,事务性内存一直是数据库设计中的基本机制,后续也有很多关于事务性内存的工作。Larus和Rajwar提供了一份最近的调查。gydF4y2Ba14gydF4y2Ba

关键思想是,包括嵌套调用在内的代码块可以用gydF4y2Ba原子gydF4y2Ba块,保证它相对于其他原子块是原子运行的。事务性内存可以使用gydF4y2Ba乐观同步。gydF4y2Ba而不是锁,一个gydF4y2Ba原子gydF4y2Ba块在没有锁定的情况下运行,累积了一个线程本地gydF4y2Ba事务日志gydF4y2Ba记录了它所有的读写记录。当块完成时,它首先gydF4y2Ba验证gydF4y2Ba它的日志,检查它已经看到了一个一致的内存视图,然后gydF4y2Ba提交gydF4y2Ba它对内存的改变。如果验证失败,因为该方法读取的内存在块执行期间被另一个线程更改了,那么块将从头重新执行。gydF4y2Ba

适当实现的事务内存消除了许多困扰基于锁编程的低级困难。没有锁引起的死锁(因为没有锁);没有优先级反转;而且在粒度和并发性之间没有令人痛苦的紧张关系。然而,最初的工作在组合良好的事务抽象方面进展甚微。有三个特别的问题。gydF4y2Ba

首先,由于事务可以自动重新运行,所以它不能做任何不可撤销的事情。例如,事务gydF4y2Ba

ins03.gifgydF4y2Ba

如果再次发射,可能会发射第二轮导弹。如果线程在读取后被取消计划,它也可能无意中发射导弹gydF4y2BangydF4y2Ba但在阅读gydF4y2BakgydF4y2Ba,和另一个在线程恢复之前都被修改的线程。这个问题需要一个保证的身体gydF4y2Ba原子gydF4y2Ba块只能执行内存操作,因此只能对自己的事务日志进行良性修改,而不能执行不可撤销的输入/输出。gydF4y2Ba

其次,许多系统不支持同步gydF4y2Ba之间的gydF4y2Ba事务,以及那些依赖于程序员提供的布尔保护的gydF4y2Ba原子gydF4y2Ba块。gydF4y2Ba8gydF4y2Ba例如,从缓冲区获取项的方法可能是:gydF4y2Ba

ins04.gifgydF4y2Ba

线程等待直到守卫(gydF4y2Ban_items > 0gydF4y2Ba),在执行块之前。但是我们怎么能取两个gydF4y2Ba连续gydF4y2Ba物品吗?我们不能打电话gydF4y2Ba得到gydF4y2Ba();gydF4y2Ba得到gydF4y2Ba(),因为另一个线程可能执行插入操作gydF4y2Ba得到gydF4y2Ba.我们可以试着打两个电话给gydF4y2Ba得到gydF4y2Ba在一个嵌套gydF4y2Ba原子gydF4y2Ba块,但它的语义不清楚,除非外部块检查缓冲区中是否有两个项。这对抽象来说是一场灾难,因为客户机(想要获得这两项)必须知道实现的内部细节。如果涉及到几个独立的抽象,情况会更糟。gydF4y2Ba

第三,以前没有事务性内存支持gydF4y2Ba的选择,gydF4y2Ba例证的gydF4y2Ba选择gydF4y2Ba前面提到的例子。gydF4y2Ba

我们将通过在声明性语言Concurrent Haskell的上下文中展示事务性内存来解决所有这三个问题,接下来我们将简要回顾这一语言。gydF4y2Ba

*gydF4y2Ba2.2.并发HaskellgydF4y2Ba

并发HaskellgydF4y2Ba20.gydF4y2Ba是Haskell 98的扩展,Haskell 98是一种纯粹的、惰性的函数式语言。它为线程之间的通信提供显式的分叉和抽象。这自然涉及到副作用,因此,考虑到懒惰评估策略,有必要能够准确地控制副作用发生的时间。这一重大突破来自于一种叫做gydF4y2Ba单体gydF4y2Ba.gydF4y2Ba21gydF4y2Ba

这里是关键思想:类型的值gydF4y2BaIOgydF4y2Ba一个是一个gydF4y2BaI / O操作gydF4y2Ba在执行时,可能会在产生type值之前执行一些I/OgydF4y2Ba一个gydF4y2Ba.例如,函数gydF4y2BaputChargydF4y2Ba而且gydF4y2Ba获取字符gydF4y2Ba类型:gydF4y2Ba

ins05.gifgydF4y2Ba

也就是说,gydF4y2BaputChargydF4y2Ba需要一个gydF4y2Ba字符gydF4y2Ba并传递I/O操作,该操作执行时将字符打印到标准输出上;而gydF4y2Ba获取字符gydF4y2Ba是一个操作,在执行时从控制台读取一个字符并将其作为操作的结果交付。一个完整的程序必须定义一个I/O操作gydF4y2Ba主要gydF4y2Ba;执行程序意味着执行该操作。例如:gydF4y2Ba

ins06.gifgydF4y2Ba

I/O动作可以用一个gydF4y2Ba一元绑定gydF4y2Ba选择符。这通常通过一些语法糖来使用,允许类似c的语法。例如,下面是一个完整的程序,它读取一个字符,然后将其打印两次:gydF4y2Ba

ins07.gifgydF4y2Ba

除了执行外部输入/输出,I/O操作还包括对gydF4y2Ba可变的细胞。gydF4y2Ba类型的值gydF4y2BaIORefgydF4y2BaA是一个可变存储单元,它可以保存类型为A的值,(仅)通过以下接口进行操作:gydF4y2Ba

ins08.gifgydF4y2Ba

newOIRefgydF4y2Ba取类型a的值并创建一个保存该值的可变存储位置。gydF4y2BareadOIRefgydF4y2Ba获取该位置的引用并返回其包含的值。gydF4y2BawriteIORefgydF4y2Ba提供相应的更新操作。类中的操作只能创建、读取和写入这些单元格gydF4y2BaIOgydF4y2BaMonad,有一个类型安全的保证,普通函数不受状态的影响,例如,一个纯函数sin不能读或写gydF4y2BaIORefgydF4y2Ba因为gydF4y2Ba罪gydF4y2Ba有类型gydF4y2Ba浮- >浮gydF4y2Ba.gydF4y2Ba

并发Haskell支持线程,每个线程独立执行输入/输出。线程是使用函数创建的gydF4y2BaforkIOgydF4y2Ba:gydF4y2Ba

ins09.gifgydF4y2Ba

forkIOgydF4y2Ba接受一个I/O操作作为参数,生成一个新线程来执行该操作,并立即将其线程标识符返回给调用者。例如,下面是一个程序,它分叉一个线程,输出'x',而主线程继续输出'y':gydF4y2Ba

ins10.gifgydF4y2Ba

Peyton Jones更全面地介绍了并发、I/O、异常和跨语言接口(纯粹的、懒惰的、函数式编程的“笨拙小组”),gydF4y2Ba18gydF4y2BaDaume III提供了Haskell的通用在线教程。gydF4y2Ba6gydF4y2Ba

回到顶部gydF4y2Ba

3.可组合交易gydF4y2Ba

我们现在准备提出论文的主要观点。我们的出发点是:gydF4y2Ba纯粹的声明性语言是事务性内存的完美设置,gydF4y2Ba有两个原因。首先,类型系统显式地将可能有副作用的计算与无效果的计算分开。正如我们将看到的,很容易改进它,以便事务可以执行内存效应,但不能执行不可撤销的输入/输出效应。其次,对可变单元格的读写是显式的,而且相对罕见:大多数计算都发生在纯函数世界中。这些函数计算执行很多很多的内存操作——定位,更新坦克,栈操作等等——但是这些操作都不需要被STM跟踪,因为它们是纯粹的,从来不需要回滚。只有相对罕见的显式操作需要记录,因此软件实现是完全合适的。gydF4y2Ba

因此,我们的方法是使用Haskell作为一种“实验室”,在其中研究具有非常表达性类型系统的设置中的事务性内存的思想。中提到的原语gydF4y2Ba扫描隧道显微镜gydF4y2Ba库,其接口在gydF4y2Ba图1gydF4y2Ba.在本文中,我们将重点介绍如何使用STM构建简单的并发抽象。原来的纸gydF4y2Ba9gydF4y2Ba通过我们在实现的同时开发的操作语义正式定义设计的细节;我们发现这在突出显示结构之间的交互时是非常宝贵的,例如,如果在原子块深处引发异常,嵌套在catch处理程序和gydF4y2BaorElsegydF4y2Ba?现在我们回到更简单的例子。gydF4y2Ba

*gydF4y2Ba3.1.事务变量和原子性gydF4y2Ba

假设我们希望实现一个资源管理器,它包含一个整数值的资源。电话(gydF4y2BagetR r ngydF4y2Ba)应该获得gydF4y2BangydF4y2Ba单位的资源gydF4y2BargydF4y2Ba阻塞,如果gydF4y2BargydF4y2Ba拥有足够的资源;电话(gydF4y2BaputR r ngydF4y2Ba)应该返回gydF4y2BangydF4y2Ba资源单位gydF4y2BargydF4y2Ba.gydF4y2Ba

下面是我们可能的编程方式gydF4y2BaputRgydF4y2Ba在STM-Haskell:gydF4y2Ba

ins11.gifgydF4y2Ba

当前可用的资源保存在gydF4y2Ba事务性的变量gydF4y2Ba类型的gydF4y2BaTVar IntgydF4y2Ba.的gydF4y2Ba类型gydF4y2Ba声明只是为该类型提供一个名称。这个函数gydF4y2BaputRgydF4y2Ba读取的值gydF4y2BavgydF4y2Ba,并回写(gydF4y2Bav +我gydF4y2Ba)进入同一个牢房。(我们讨论gydF4y2BagetRgydF4y2Ba接下来,在第3.2节。)gydF4y2Ba

的gydF4y2BareadTVargydF4y2Ba而且gydF4y2BawriteTVargydF4y2Ba操作都返回STM操作(gydF4y2Ba图1gydF4y2Ba),但是Haskell允许我们使用相同的do{…}语法来组成STM操作,就像我们对I/O操作所做的那样。这些STM操作在执行过程中保持试探性:为了向系统的其余部分公开STM操作,可以将其传递给一个新函数gydF4y2Ba原子gydF4y2Ba型:gydF4y2Ba

ins12.gifgydF4y2Ba

它接受一个类型为的内存事务gydF4y2BaSTM一gydF4y2Ba,并传递一个I/O操作,该操作在执行时,相对于所有其他内存事务原子地运行事务。有人可能会说:gydF4y2Ba

ins13.gifgydF4y2Ba

底层事务内存负责维护每个线程的事务日志,以记录对的临时访问gydF4y2BaTVarsgydF4y2Ba.当gydF4y2Ba原子gydF4y2Ba调用时,STM检查记录的访问是gydF4y2Ba有效的gydF4y2Ba也就是说,没有并发事务向这些事务提交冲突的更新gydF4y2BaTVarsgydF4y2Ba.如果日志有效,则STMgydF4y2Ba提交gydF4y2Ba它以原子的方式传递给堆,从而将其影响暴露给其他事务。否则,将使用新的日志重新运行内存事务。gydF4y2Ba

将世界划分为STM操作和I/O操作提供了两个有价值的属性,都由类型系统静态检查:gydF4y2Ba

  • 没有办法在一个事务中执行一般的I/O,因为没有操作接受IO计算并在STM单子中执行它。因此,只能在内存事务中执行STM操作和纯计算。这正是我们在第2.1节中寻求的保证。它静态地阻止程序员调用gydF4y2BalaunchMissilesgydF4y2Ba因为发射导弹是一个带类型的I/O操作gydF4y2BaIO ()gydF4y2Ba,并且不能与STM动作组合。gydF4y2Ba
  • 任何STM操作都不能在事务之外执行,因此程序员不会意外地读或写gydF4y2BaTVargydF4y2Ba没有保护gydF4y2Ba原子gydF4y2Ba.当然,人们总是可以这么说gydF4y2Ba原子gydF4y2Ba(gydF4y2BareadTVar vgydF4y2Ba)阅读gydF4y2BaTVargydF4y2Ba在一个琐碎的事务中,却呼唤着gydF4y2Ba原子gydF4y2Ba不能省略。gydF4y2Ba

*gydF4y2Ba3.2.阻止内存事务gydF4y2Ba

任何并发机制都必须为线程提供一种方法来等待一个或多个由其他线程引起的事件。在基于锁的编程中,这通常是使用条件变量完成的;基于消息的系统提供了一个构造来等待多个通道上的消息;POSIX提供gydF4y2Ba选择gydF4y2Ba;Win32提供gydF4y2BaWaitForMultipleObjectsgydF4y2Ba;而且gydF4y2Ba扫描隧道显微镜gydF4y2Ba迄今为止的系统允许程序员用布尔条件来保护原子块(见第2.1节)。gydF4y2Ba

Haskell设置让我们得到了一种非常简单的阻塞新机制。此外,正如我们在第3.3和3.4节中所介绍的,它以基于锁的编程所不可能的方式支持组合。gydF4y2Ba

这个想法是提供一个gydF4y2Ba重试gydF4y2Ba操作,以指示当前原子操作尚未准备好运行到完成。这是……的代码gydF4y2BagetRgydF4y2Ba:gydF4y2Ba

ins14.gifgydF4y2Ba

它读取资源的值v,如果v >= i,则减少i。如果v < i,则表示变量中资源不足,在这种情况下调用gydF4y2Ba重试gydF4y2Ba.从概念上讲,gydF4y2Ba重试gydF4y2Ba在没有任何效果的情况下中止事务,并在开始时重新启动它。然而,实际上重新执行事务是没有意义的,直到gydF4y2Ba至少有一个gydF4y2BaTVargydF4y2BaS已被另一个线程写入。令人高兴的是,事务日志(无论如何都是需要的)已经准确地记录了这些gydF4y2BaTVargydF4y2Ba年代是阅读。因此,该实现将阻塞线程,直到更新其中至少一个。请注意,gydF4y2Ba重试gydF4y2Ba的类型(gydF4y2BaSTM一gydF4y2Ba)允许它在任何可能发生STM动作的地方使用。gydF4y2Ba

与自动隐式的验证检查不同,gydF4y2Ba重试gydF4y2Ba由程序员显式调用。它并不表示任何不好或意想不到的事情;相反,当在其他并发性方法中发生某种阻塞时,它就会出现。gydF4y2Ba

注意,不需要gydF4y2BaputRgydF4y2Ba操作来记住给任何条件变量发信号。只要写信给gydF4y2BaTVargydF4y2BaS参与进来,生产者就会唤醒消费者。这样就消除了一整类失去唤醒的bug。gydF4y2Ba

从效率的角度来看,尽可能早地调用重试是有意义的,并且在测试成功之前避免读取不相关的位置。尽管如此,编程接口还是非常简单,易于推理。gydF4y2Ba

*gydF4y2Ba3.3.顺序组成gydF4y2Ba

通过使用gydF4y2Ba原子gydF4y2Ba,程序员识别原子事务,在经典意义上,它包含的整个操作集似乎不可分割地发生。这是用于并发抽象的顺序组合的关键。例如,要获取一个资源的三个单位和另一个资源的七个单位,线程可以这样说gydF4y2Ba

ins15.gifgydF4y2Ba

标准do {..;}符号结合了两者的STM操作gydF4y2BagetRgydF4y2Ba调用和底层事务内存将其更新提交为单个原子I/O操作。gydF4y2Ba

的gydF4y2Ba重试gydF4y2Ba当事务可能阻塞时,函数是使事务可组合的核心。上面的事务将会阻塞gydF4y2Bar1gydF4y2Ba或gydF4y2Bar2gydF4y2Ba资源不足:没有必要让调用者知道怎么回事gydF4y2BagetRgydF4y2Ba是实现的,或者什么条件保证它成功。等待也不会有任何陷入僵局的风险gydF4y2Bar2gydF4y2Ba而持有gydF4y2Bar1gydF4y2Ba.gydF4y2Ba

这种组合STM操作的能力就是我们没有定义的原因gydF4y2BagetRgydF4y2Ba作为一个I/O操作,包装在一个调用gydF4y2Ba原子gydF4y2Ba.通过将其保留为STM操作,我们允许程序员将其与其他STM操作组合在一起,然后最终将其密封到与的事务中gydF4y2Ba原子gydF4y2Ba.在基于锁的设置中,人们会担心关键锁在两个调用之间被释放,如果另一个线程以相反的顺序获取资源,则会担心死锁,但这里不存在这样的问题。gydF4y2Ba

的gydF4y2Ba扫描隧道显微镜gydF4y2Ba原子动作上的类型提供了一个强有力的保证:gydF4y2Ba只有gydF4y2Ba执行动作的方式是将其传递给gydF4y2Ba原子gydF4y2Ba.gydF4y2Ba任何STM操作都可以与其他STM操作健壮地组合在一起:结果序列仍将原子地执行。gydF4y2Ba

*gydF4y2Ba3.4.组合选择gydF4y2Ba

我们已经讨论了在中的组合事务gydF4y2Ba序列,gydF4y2Ba这样两个都被执行了。STM-Haskell还允许我们将事务组合为gydF4y2Ba选择,gydF4y2Ba所以只有一个被执行。例如,to getgydF4y2Ba要么gydF4y2Ba3单位从gydF4y2Bar1gydF4y2Ba或7gydF4y2Ba单位从gydF4y2Bar2gydF4y2Ba:gydF4y2Ba

ins16.gifgydF4y2Ba

的gydF4y2BaorElsegydF4y2Ba函数提供gydF4y2Ba扫描隧道显微镜gydF4y2Ba模块(gydF4y2Ba图1gydF4y2Ba);在这里,它被写为中缀,用反引号括起来,但它是一个非常普通的函数,有两个参数。事务gydF4y2Bas1 ' orElse ' s2gydF4y2Ba第一次运行gydF4y2Bas1gydF4y2Ba;如果gydF4y2Bas1gydF4y2Ba调用gydF4y2Ba重试gydF4y2Ba,然后gydF4y2Bas1gydF4y2Ba被抛弃了,没有效果,还有gydF4y2Bas2gydF4y2Ba运行。如果gydF4y2Bas2gydF4y2Ba也称gydF4y2Ba重试gydF4y2Ba然后整个调用进行检索,但它等待被读取的变量gydF4y2Ba要么gydF4y2Ba两个嵌套事务(即两个变量集的并集)的。的启用条件,程序员不需要知道gydF4y2Bas1gydF4y2Ba而且gydF4y2Bas2gydF4y2Ba.gydF4y2Ba

使用gydF4y2BaorElsegydF4y2Ba为库实施者提供了一种优雅的方式,使其能够听从调用者决定是否阻塞的问题。例如,转换的阻塞版本很简单gydF4y2BagetRgydF4y2Ba转换为一个返回布尔成功或失败的结果:gydF4y2Ba

ins17.gifgydF4y2Ba

如果gydF4y2BagetRgydF4y2Ba正常完成,gydF4y2BanonBlockGetRgydF4y2Ba将返回gydF4y2Ba真正的gydF4y2Ba;另一方面,如果gydF4y2BagetRgydF4y2Ba阻塞(即重试)、gydF4y2BaorElsegydF4y2Ba是否会尝试第二种选择,并立即成功返回gydF4y2Ba假gydF4y2Ba.注意,这个习语依赖于的左偏性质gydF4y2BaorElsegydF4y2Ba.同样的结构也可以用于从返回布尔结果的操作构建阻塞操作:简单地调用gydF4y2Ba重试gydF4y2Ba收到一个gydF4y2Ba假gydF4y2Ba结果:gydF4y2Ba

ins18.gifgydF4y2Ba

的gydF4y2BaorElsegydF4y2Ba函数遵循有用的规律:它是结合的,有单位gydF4y2Ba重试gydF4y2Ba:gydF4y2Ba

ins19.gifgydF4y2Ba

Haskell的狂热爱好者会意识到STM可能是MonadPlus的一个实例。gydF4y2Ba

*gydF4y2Ba3.5.异常gydF4y2Ba

STM单子支持异常就像gydF4y2BaIOgydF4y2Bamonad,和c#非常相似。两个新的原始函数,gydF4y2Ba抓gydF4y2Ba而且gydF4y2Ba扔gydF4y2Ba是必需的;它们的类型是给定的gydF4y2Ba图1gydF4y2Ba.问题是:事务和异常应该如何交互?例如,这个事务应该做什么?gydF4y2Ba

ins20.gifgydF4y2Ba

如果,程序员抛出异常gydF4y2Ban > limgydF4y2Ba,这种情况下…gydF4y2Ba写入数据gydF4y2Ba...p一个rt will clearly not take place. But what about the write tov_ngydF4y2Ba在抛出异常之前?gydF4y2Ba

并发Haskell鼓励程序员将异常用于信号错误条件,而不是用于正常的控制流。内置异常,例如除以0,也属于这一类。为了一致性,那么,在上面的程序中gydF4y2Ba我们不希望程序员不得不考虑异常的可能性,gydF4y2Ba当推断如果时gydF4y2Bav_ngydF4y2Ba(明显地)写入,然后将数据写入缓冲区。因此,我们指定异常具有gydF4y2Ba中止的语义:gydF4y2Ba如果原子事务抛出异常,则必须验证该事务,就像它已经正常完成一样;但是,没有提交任何更改。如果验证成功,则传播异常;但是,如果验证失败,则异常的抛出可能基于不一致的内存视图,因此异常将被丢弃,事务将从头重新执行。中止语义使推断不变量变得容易得多:程序员只需担心在事务提交时不变量是否被保留;根据定义,事务期间引发的异常总是恢复不变量。gydF4y2Ba

我们使用异常中止gydF4y2Ba原子gydF4y2BaBlocks是一种自由的设计选择。在其他语言中,特别是在异常使用更频繁的语言中,区分导致封闭的异常可能是合适的gydF4y2Ba原子gydF4y2Ba阻塞从允许它在传播之前提交的异常中中止。gydF4y2Ba

注意呼叫之间的区别gydF4y2Ba扔gydF4y2Ba和调用gydF4y2Ba重试gydF4y2Ba.前者发出错误信号,并中止事务;后者仅表示事务尚未准备好运行,并在情况发生变化时重新执行它。gydF4y2Ba

异常可以将值携带出STM世界。例如,考虑gydF4y2Ba

ins21.gifgydF4y2Ba

在这里,外部世界将看到包含字符串的异常值gydF4y2Ba年代gydF4y2Ba这是从gydF4y2BaTVargydF4y2Ba.但是,由于事务在异常传播之前被中止,因此其写入gydF4y2BasvargydF4y2Ba是外部无法观察到的。有人可能会争辩说,允许从流产的事务中“泄漏”读是错误的,但我们不同意。异常所携带的值只能表示堆的一致视图(否则验证将失败,事务将在不传播异常的情况下重新执行),并且几乎不可能调试一个只表示“发生了不好的事情”的错误条件,而故意丢弃有关不好的事情是什么的所有线索。基本的交易保证不会受到威胁。gydF4y2Ba

如果异常携带一个gydF4y2BaTVargydF4y2Ba分配gydF4y2Ba在终止的事务中?悬空的指针会让人不舒服。为了避免这种情况,我们对异常的语义进行了细化,以说明抛出异常的事务就其写入效果而言是被中止的,但是它的gydF4y2Ba分配gydF4y2Ba影响保留;毕竟,它们是线程本地的。结果,gydF4y2BaTVargydF4y2Ba在事务之后可见,处于分配时的状态。像这样的情况是棘手的,这就是为什么我们开发了一个完整的正式语义。gydF4y2Ba9gydF4y2Ba

并发Haskell还提供了异步异常,这些异常可以作为信号抛出到线程中,典型的例子是错误情况,如堆栈溢出,或当主线程希望关闭辅助线程时。如果线程处于STM事务中,则可以丢弃事务日志,而不会产生外部可见的影响。gydF4y2Ba

如果内部引发异常怎么办gydF4y2BaorElsegydF4y2Ba?我们考虑了这样一种设计:如果第一种方案引发异常,我们可以放弃它的影响,转而尝试第二种方案。但那将使美丽的身份失效gydF4y2Ba重试gydF4y2Ba一个单位的gydF4y2BaorElsegydF4y2Ba也会使gydF4y2BaorElsegydF4y2Ba异常的处理不对称(从第一个替代方案中丢弃,但由第二个替代方案传播)。因此,我们选择了例外gydF4y2Ba做gydF4y2Ba从第一个备选方案传播:只有在第一个备选方案调用a时才检查第二个备选方案gydF4y2Ba重试gydF4y2Ba.gydF4y2Ba

在类中捕获异常呢gydF4y2Ba原子gydF4y2Ba块吗?考虑一下这个例子:gydF4y2Ba

ins22.gifgydF4y2Ba

如果gydF4y2BaggydF4y2Ba的作者出错(抛出异常)gydF4y2BafgydF4y2Ba可能会合理地想要确保gydF4y2Ba项gydF4y2Ba不从端口读取吗gydF4y2BapgydF4y2Ba然后丢弃。事实上,如果gydF4y2BafgydF4y2Ba被称为gydF4y2Ba原子gydF4y2Ba上下文,如gydF4y2Ba原子(f p)gydF4y2Ba的影响。gydF4y2BareadPortgydF4y2Ba都被丢弃了,所以gydF4y2Ba项gydF4y2Ba不读。但假设gydF4y2BafgydF4y2Ba是否在捕获异常的上下文中调用gydF4y2Ba在离开STM世界之前:gydF4y2Ba

ins23.gifgydF4y2Ba

在我们的原始论文中,我们提出(gydF4y2Baf₁gydF4y2Ba)将被保留并对调用可见(gydF4y2Baf p2gydF4y2Ba)。此外,如果后者成功而自身没有抛出异常或重新尝试,则(gydF4y2Baf₁gydF4y2Ba)将被永久关押。gydF4y2Ba

最终,我们觉得这种处理异常之前的效果的方法似乎不一致。考虑到gydF4y2BafgydF4y2Ba;以确保该项确实没有被读取gydF4y2BaggydF4y2Ba抛出一个异常,他可以尝试这样做:gydF4y2Ba

ins24.gifgydF4y2Ba

但这取决于gydF4y2BaunReadPortgydF4y2Ba手动复制底层STM支持的回滚。结论是明确的:第一个论点的影响gydF4y2Ba抓gydF4y2Ba如果计算引发异常,则应返回。同样,这在STM-Haskell上下文中很好地解决了问题,因为gydF4y2Ba抓gydF4y2Ba这里使用的操作有一个gydF4y2Ba扫描隧道显微镜gydF4y2Ba类型,它向程序员指示代码是事务性的。gydF4y2Ba

回到顶部gydF4y2Ba

4.应用程序和例子gydF4y2Ba

在本节中,我们将提供一些示例,介绍如何使用可组合内存事务构建更高级别的并发抽象。我们关注那些可能会阻塞线程之间通信的操作。以前的工作已经多次表明,如何使用事务性内存操作从顺序代码开发标准共享内存数据结构。gydF4y2Ba8gydF4y2Ba,gydF4y2Ba11gydF4y2Ba

*gydF4y2Ba4.1.兆乏gydF4y2Ba

在我们的STM工作之前,并发Haskell提供了gydF4y2Ba兆乏gydF4y2Ba作为允许线程安全通信的基本机制。一个gydF4y2Ba兆乏gydF4y2Ba可变位置像a吗gydF4y2BaTVargydF4y2Ba但也有可能是两者之一gydF4y2Ba空的,gydF4y2Ba或gydF4y2Ba完整的gydF4y2Ba与一个值。的gydF4y2Ba以兆乏gydF4y2Ba函数留下满gydF4y2Ba兆乏gydF4y2Ba空的,但街区上的一个空gydF4y2Ba兆乏gydF4y2Ba.一个gydF4y2BaputMVargydF4y2Ba在一个空的gydF4y2Ba兆乏gydF4y2Ba让它满了,但满了就挡住了gydF4y2Ba兆乏gydF4y2Ba.所以gydF4y2Ba兆乏gydF4y2BaS实际上是一个单点信道。gydF4y2Ba

它很容易实现gydF4y2Ba兆乏gydF4y2BaS在顶端gydF4y2BaTVargydF4y2Ba年代。gydF4y2Ba兆乏gydF4y2Ba保存类型为a的值可以用a表示gydF4y2BaTVargydF4y2Ba保存类型的值gydF4y2Ba也许是一个gydF4y2Ba;这种类型要么是空值("gydF4y2Ba没有什么gydF4y2Ba),或者实际上是a(例如,“gydF4y2Ba只有42gydF4y2Ba”)。gydF4y2Ba

ins25.gifgydF4y2Ba

的gydF4y2BatakeMVargydF4y2Ba方法的内容gydF4y2BaTVargydF4y2Ba并重试,直到它看到的值不是gydF4y2Ba没有什么gydF4y2Ba:gydF4y2Ba

ins26.gifgydF4y2Ba

相应的gydF4y2BaputMVargydF4y2Ba操作重试,直到它看到为止gydF4y2Ba没有什么gydF4y2Ba,这时它会更新底层的gydF4y2BaTVargydF4y2Ba:gydF4y2Ba

ins27.gifgydF4y2Ba

注意如何直接从这些阻塞设计构建返回布尔成功/失败结果的操作。例如:gydF4y2Ba

ins28.gifgydF4y2Ba

*gydF4y2Ba4.2.多播频道gydF4y2Ba

兆乏gydF4y2Ba有效地为单个缓冲项提供通信通道。在本节中,我们将展示如何编程缓冲、多项、多播通道,其中写入通道的项(gydF4y2BawriteMChangydF4y2Ba)在内部进行缓冲,由通道创建的每个读端口接收一次。完整界面为:gydF4y2Ba

ins29.gifgydF4y2Ba

我们用链表或gydF4y2Ba链gydF4y2Ba,在每个尾部都有一个事务变量,因此它可以扩展为gydF4y2BawriteMChangydF4y2Ba:gydF4y2Ba

ins30.gifgydF4y2Ba

一个gydF4y2BaMChangydF4y2Ba表示为指向链的“写”端的可变指针,而gydF4y2Ba港口gydF4y2Ba指向读端:gydF4y2Ba

ins31.gifgydF4y2Ba

有了这些定义,代码自己编写:gydF4y2Ba

ins32.gifgydF4y2Ba

注意使用gydF4y2Ba重试gydF4y2Ba阻止gydF4y2BareadPortgydF4y2Ba当缓冲区为空时。虽然这个实现非常简单,但它确保写入gydF4y2BaMChangydF4y2Ba送到每一个gydF4y2Ba港口gydF4y2Ba;它允许多个写入器(它们的写入是交错的);它允许每个端口上有多个读取器(一个读取器读取的数据不会被该端口上的其他读取器看到);当一个端口被丢弃时,垃圾收集器会恢复缓冲的数据。gydF4y2Ba

更复杂的变量很容易编程。例如,假设我们希望确保编写器得到的数据不超过gydF4y2BaNgydF4y2Ba项目前面最先进的阅读器。要做到这一点,一种方法是让作者包含一个连续递增gydF4y2BaIntgydF4y2Ba在每一个gydF4y2Ba项gydF4y2Ba,并有一个共享gydF4y2BaTVargydF4y2Ba保持到目前为止任何读取器读取的最大序列号。让读者保持更新是很简单的,作者在添加另一项之前也可以查阅它。gydF4y2Ba

*gydF4y2Ba4.3.合并gydF4y2Ba

我们已经强调过,交易是gydF4y2Ba可组合。gydF4y2Ba例如,要从两个不同的多播通道中读取,我们可以说:gydF4y2Ba

ins33.gifgydF4y2Ba

不需要对任何一个多播通道进行更改。如果两个端口都没有任何数据,STM机制将导致线程在gydF4y2BaTVargydF4y2BaS在每个通道的末端。gydF4y2Ba

同样,程序员也可以等待一个条件,该条件涉及到gydF4y2Ba兆乏gydF4y2Ba年代和gydF4y2BaMChangydF4y2BaS(也许多播通道表示普通数据和gydF4y2Ba兆乏gydF4y2Ba正在被用来发出终止请求的信号),例如:gydF4y2Ba

ins34.gifgydF4y2Ba

这个示例是为了简洁而设计的,但它展示了如何组合从不同库中提取的操作(实现时不期望它们一起使用)。在最一般的情况下,我们可以选择从许多不同来源接收的值。给出一个类型的计算列表gydF4y2BaSTM一gydF4y2Ba我们可以通过定义一个合并操作符来获取它们产生的第一个值:gydF4y2Ba

ins35.gifgydF4y2Ba

(函数gydF4y2Bafoldr1 fgydF4y2Ba简单地减少列表[gydF4y2Ba一个gydF4y2Ba1gydF4y2BabgydF4y2Ba2gydF4y2Ba...一个gydF4y2BangydF4y2Ba到值gydF4y2Ba一个gydF4y2Ba1gydF4y2BafgydF4y2Ba一个gydF4y2Ba2gydF4y2Baf……fgydF4y2Ba一个gydF4y2BangydF4y2Ba)。这个例子在STM-Haskell中非常简单。相反,类型的函数gydF4y2Ba

ins36.gifgydF4y2Ba

在Concurrent Haskell中是不可实现的,或者在其他由互斥锁和条件变量构建的操作中也是不可实现的。gydF4y2Ba

回到顶部gydF4y2Ba

5.实现gydF4y2Ba

从我们最初的论文开始,已经有很多关于构建STM的快速实现以及硬件支持来替换或加速它们的工作。gydF4y2Ba14gydF4y2Ba我们在STM-Haskell中使用的技术是大部分此类工作的典型,因此我们在这里不深入讨论细节。但是,总而言之,当事务运行时,它会构建一个私有日志,记录gydF4y2BaTVargydF4y2BaS,它从它们中读取的值,以及(在写入的情况下)它想要存储到它们中的新值。当事务试图提交时,它必须将此日志与堆协调起来。从逻辑上讲,这有两个步骤:gydF4y2Ba验证gydF4y2Ba事务来检查是否已经读取了位置的冲突更新,然后gydF4y2Ba的回信gydF4y2Ba更新的gydF4y2BaTVargydF4y2BaS已被修改。gydF4y2Ba

然而,gydF4y2Ba重试gydF4y2Ba而且gydF4y2BaorElsegydF4y2Ba抽象使我们更加仔细地考虑如何将阻塞操作与这种通用方法集成在一起。跟进哈里斯和弗雷泽的研究gydF4y2Ba8gydF4y2Ba我们建立了gydF4y2Ba重试gydF4y2Ba通过使用事务的日志来识别gydF4y2BaTVargydF4y2BaS,然后添加“绊线”gydF4y2BaTVargydF4y2BaS在阻塞之前:任何这些的后续更新gydF4y2BaTVargydF4y2BaS将解除线程阻塞。gydF4y2Ba

的gydF4y2BaorElsegydF4y2Ba而且gydF4y2Ba抓gydF4y2Ba构造都是使用封闭嵌套事务实现的gydF4y2Ba17gydF4y2Ba因此,可以在不丢弃外部事务的情况下回滚由所包含的工作所做的更新。在我们最初的论文中,有一个微妙之处我们没有注意到:如果回滚所附的事务gydF4y2Ba然后它读取的位置日志必须由父进程保留。gydF4y2Ba回过头来看,原因很清楚——是否回滚的决定必须在与外部事务相同的原子点上进行验证。gydF4y2Ba

*gydF4y2Ba5.1.进步gydF4y2Ba

STM实现保证只有当第一个事务提交时,一个事务才能强制另一个事务中止。因此,STM实现是gydF4y2Ba无锁gydF4y2Ba从某种意义上说,它保证任何时候某些正在运行的事务都能成功提交。例如,如果一个事务读和写gydF4y2BaTVargydF4y2BaxgydF4y2Ba然后gydF4y2BaTVargydF4y2Bay,gydF4y2Ba而另一个则对其进行读写gydF4y2BaTVargydF4y2BaS的相反顺序。每笔交易将遵循这些原始价值gydF4y2BaTVargydF4y2Ba年代;第一个进行验证的将提交,第二个将中止并重新启动。同样,同步也会发生冲突gydF4y2BaTVargydF4y2BaS不能导致循环重新启动,即两个或多个事务重复地相互中止。gydF4y2Ba

然而,饥饿是可能的。例如,一个运行了很长时间的事务可能会被与之冲突的较短事务反复中止。我们认为饥饿在实际中不太可能发生,但没有进一步的经验我们无法判断。如果事务正在等待一个永远不为真的条件,它也可能永远不会提交。gydF4y2Ba

回到顶部gydF4y2Ba

6.相关工作gydF4y2Ba

事务长期以来一直被用于数据库中的容错gydF4y2Ba7gydF4y2Ba和分布式系统。这些事务依赖于稳定的存储和分布式提交协议,以保护系统完整性,防止崩溃和通信故障。我们正在研究的事务性内存提供了对单个进程内内存的访问;它不打算在崩溃中幸存下来,因此不需要分布式提交协议或稳定存储。由此可见,许多设计和实现问题与分布式或仅持久性事务系统中产生的问题有很大的不同。TM最初是作为一种硬件架构提出的gydF4y2Ba12gydF4y2Ba,gydF4y2Ba24gydF4y2Ba支持非阻塞同步和对该模型的体系结构支持仍然是正在进行的研究的主题,在软件中构建有效的实现也是如此。Larus和Rajwar提供了对实现技术的最新调查。gydF4y2Ba14gydF4y2Ba

事务组合需要能够运行任意大小和持续时间的事务,这对基于硬件的事务内存设计提出了挑战,因为它们本身资源有限。硬件支持大型事务的一种方法是虚拟化,gydF4y2Ba4gydF4y2Ba,gydF4y2Ba22gydF4y2Ba提供透明的溢出机制。另一种方法是混合STM设计gydF4y2Ba5gydF4y2Ba,gydF4y2Ba13gydF4y2Ba它结合了硬件和软件机制。gydF4y2Ba

在我们最初的论文之后,Carlstrom等人研究了一种形式gydF4y2Ba重试gydF4y2Ba它会监视对特定位置的更新,gydF4y2Ba3.gydF4y2Ba认为这在硬件上更容易支持,可能比我们的形式更有效gydF4y2Ba重试gydF4y2Ba.但是,除非仔细定义监视集,否则会牺牲可组合性gydF4y2Ba重试gydF4y2Ba提供是因为对非监视位置的更新可能会更改事务中的控制流。gydF4y2Ba

我们最初的文章还讨论了与并发相关的编程抽象,特别是并发MLgydF4y2Ba可组合事件gydF4y2Ba而且gydF4y2BaScheme48提出的建议。gydF4y2Ba

回到顶部gydF4y2Ba

7.结论gydF4y2Ba

在本文中,我们介绍了来自STM-Haskell的关于可组合内存事务的思想,为并发编程提供了一种基础,它提供了比迄今为止可用的更丰富的组合:两个原子操作可以按顺序粘在一起,以保证结果将原子地运行,两个原子操作可以作为替代粘在一起,以确保其中一个操作将运行。在随后的工作中,我们进一步增强了STM接口gydF4y2Ba不变量gydF4y2Ba.gydF4y2Ba10gydF4y2Ba

我们使用Haskell作为一个特别合适的实验室来探索这些想法及其实现。一个明显的问题是:我们的结果在多大程度上可以被带回命令式编程的主流世界?这是我们和其他许多人从最初的论文开始就一直在研究的问题。可组合块的思想贯穿始终gydF4y2Ba重试gydF4y2Ba而且gydF4y2BaorElsegydF4y2Ba在其他设置主题中应用似乎很简单,当然,在较低的系统级别中支持阻塞和唤醒。gydF4y2Ba

一个更微妙的问题是如何应用事务状态和非事务状态之间的分离,或者如何应用事务代码和非事务代码之间的分离。在Haskell中,可变状态和不纯代码被认为是例外而不是常态,因此区分少量的不纯事务代码和少量的不纯非事务代码似乎是合理的;在任何情况下,两者都可以调用纯函数。gydF4y2Ba

相比之下,在主流语言中,大多数代码都是使用可变状态以非纯风格编写的。这就产生了一种张力:静态分离事务处理代码和数据保留了STM-Haskell的强大保证(没有不可撤销的对“gydF4y2BalaunchMissilesgydF4y2Ba,并且不通过STM接口就不能直接访问事务状态),但是它需要复制源代码来创建库函数的事务变体,并在事务数据格式和正常数据格式之间进行封送。在这个设计空间中调查复杂的权衡是当前研究的主题。gydF4y2Ba1gydF4y2Ba,gydF4y2Ba16gydF4y2Ba

无论人们是否相信事务,一些效果系统和/或所有权类型的组合似乎确实将在并发编程语言中扮演越来越重要的角色,而这些可能有助于实现内存事务所需要的保证。gydF4y2Ba

我们的主要主张是,事务性内存从质量上提高了提供给程序员的抽象级别。就像高级语言使程序员不必担心寄存器分配一样,事务内存也使程序员不必担心设计共享内存数据结构时锁和锁获取顺序。更根本的是,人们可以在不知道它们的实现的情况下组合这些抽象,这是构造大型程序的关键。gydF4y2Ba

与高级语言一样,事务性内存并不能完全消除bug;例如,如果两个线程都在等待另一个线程的通信,那么两个线程很容易死锁。但是收获是非常可观的:事务为并发提供了一个编程平台,消除了所有类型的并发错误,并允许程序员专注于真正有趣的部分。gydF4y2Ba

回到顶部gydF4y2Ba

致谢gydF4y2Ba

我们要感谢Byron Cook, Austin Donnelly, Matthew Flatt, Jim Gray, Dan Grossman, Andres Löh, Jon Howell, Jan-Willem Maessen, Jayadev Misra, Norman Ramsey, Michael Ringenburg, David Tarditi,特别是Tony Hoare,感谢他们对本文早期版本的有益反馈,感谢Guy Steele在准备修订版时的细致建议。gydF4y2Ba

回到顶部gydF4y2Ba

参考文献gydF4y2Ba

1.Abadi, M., Birrell, A., Harris, T.和Isard, M.事务性记忆的语义和自动互斥。gydF4y2BaPOPL'08:第35届ACM SIGPLAN-SIGACT编程语言原理研讨会论文集,gydF4y2Ba6374页。ACM, 2008年1月。gydF4y2Ba

2.Blelloch, g.e., Hardwick, j.c., Sipelstein, J., Zagha, M.和Chatterjee, S.可移植嵌套数据并行语言的实现。J。gydF4y2BaDistrib平行。第一版,gydF4y2Ba9.21(1): 414、1994。gydF4y2Ba

3.Carlstrom, b.d., McDonald, A., Chafi, H., Chung, J., Minh, c.c., Kozyrakis, C和Olukotun, K. Atomos事务编程语言。gydF4y2BaPLDI’06:2006 ACMSIGPLAN编程语言设计与实现会议论文集gydF4y2Ba第113页,ACM, 2006年6月。gydF4y2Ba

4.Chung, J., Minh, c.c., McDonald, A., Skare, T., Chafi, H., Carlstrom, B.D., Kozyrakis, C,和Olukotun, K.事务性内存虚拟化的权衡。gydF4y2BaASPLOS'06:第12届编程语言和操作系统架构支持国际会议论文集,gydF4y2Ba371381页。ACM, 2006年10月。gydF4y2Ba

5.达姆伦,P,费多洛娃,A,列夫,12岁。Y., Luchangco, V., Moir, M.和Nussbaum, D.混合事务存储器。gydF4y2BaASPLOS'06:第12届编程语言和操作系统架构支持国际会议论文集,gydF4y2Ba页。336346年,13岁。ACM, 2006年10月。gydF4y2Ba

6.另一个Haskell教程,http://www.cs.utah.edu/~hal/docs/daurne02yaht.pdf, 2006。gydF4y2Ba

7.格雷,J.和路透社,A。gydF4y2Ba事务处理:概念和技术。gydF4y2Ba摩根·考夫曼出版公司1992年出版。gydF4y2Ba

8.Harris, T.和Fraser, K.轻量级事务的语言支持。gydF4y2BaOOPSLA'03:第18届ACMSIGPLAN面向对象编程、系统、语言和应用会议论文集,gydF4y2Ba第388402页,ACM, 2003年10月。gydF4y2Ba

9.Harris, T., Marlow, S., Peyton Jones, S.和Herlihy, M.可组合内存事务。gydF4y2BaPPoPP'05:第十届ACM SIGPLAN并行编程原理与实践研讨会论文集。gydF4y2Ba第4860页,ACM, 2005年6月。gydF4y2Ba

10.哈里斯,O。,而且Peyton Jones, S. Transactional memory with data invariants.第一届ACM SIGPLAN事务计算语言、编译器和硬件支持研讨会论文集,gydF4y2Ba2006年6月。gydF4y2Ba

11.Herlihy, M., Luchangco, V ., Moir, M.和Scherer, III . W.N.用于动态大小数据结构的软件事务存储器。gydF4y2BaPODC'03:第22届ACM分布式计算原理研讨会论文集,gydF4y2Ba第92101页,ACM, 2003年7月。gydF4y2Ba

12.事务性内存:对无锁数据结构的架构支持。gydF4y2Ba第20届计算机体系结构国际研讨会论文集gydF4y2Ba289300页。ACM, 1993年5月。gydF4y2Ba

13.Kumar, S., Chu, M., J. Hughes, C, Kundu, P.和Nguyen, A.混合事务记忆。gydF4y2BaPPoPP'06:第11届ACM SIGPLAN并行编程原理与实践研讨会论文集。gydF4y2Ba第209220页,ACM, 2006年3月。gydF4y2Ba

14.Larus, J.和Rajwar, R.。gydF4y2Ba事务存储器(计算机体系结构综合讲座)。gydF4y2BaMorgan & Claypool出版社,2007年。gydF4y2Ba

15.Martin, M, Blundell, C, Lewis, E.事务性内存原子性语义的微妙之处。gydF4y2Ba阿奇特莱特。gydF4y2Ba5(2): 2006。gydF4y2Ba

16.摩尔,K.F.和格罗斯曼,D.高级小步骤操作语义22。对于事务。gydF4y2BaPOPL'08:第35届ACM SIGPLAN-SIGACT编程语言原理研讨会论文集,gydF4y2Ba5162页。ACM, 2008年1月。gydF4y2Ba

17.嵌套事务:一种可靠的分布式计算方法。麻省理工学院MIT/LCS/ TR-260技术代表,1981年4月。gydF4y2Ba

18.解决棘手的小组:Haskell中的单元输入/输出、并发性、异常和外语调用。gydF4y2Ba软件建设工程理论,Marktoberdorf暑期学校2000gydF4y2Ba.gydF4y2Ba

19.佩顿·琼斯,s。在gydF4y2Ba漂亮的代码gydF4y2Ba(2007)。O ' reilly。gydF4y2Ba

20.佩顿·琼斯,S.戈登,A.和Finné S. Concurrent Haskell。gydF4y2BaPOPL'96:第23届ACM SIGPLAN-SIGACT编程语言原理研讨会论文集。gydF4y2Ba第295308页,ACM, 1996年1月。gydF4y2Ba

21.佩顿·琼斯,S.和Wadler, P.命令式函数编程。gydF4y2BaPOPL'93:第20届ACM SIGPLAN-SIGACT编程语言原理研讨会论文集。gydF4y2Ba第7184页,ACM, 1993年1月。gydF4y2Ba

22.Rajwar, R., Herlihy, M.和Lai, K.虚拟化事务存储器。gydF4y2Ba第32届计算机体系结构国际研讨会论文集gydF4y2Ba第494505页,IEEE计算机学会,2005年6月。gydF4y2Ba

23.Shavit, N.和Touitou, D.软件事务存储器。gydF4y2BaPODC'95:第14届ACM分布式计算原理研讨会论文集,gydF4y2Ba第204213页,ACM, 1995年8月。gydF4y2Ba

24.斯通,j.m.,斯通,h.s.,海德尔伯格,P,图雷克,j。多次预定和俄克拉荷马州的更新。gydF4y2Ba并行与分布式技术gydF4y2Ba1(4): 5871、1993。gydF4y2Ba

25.免费的午餐结束了:软件向并发性的根本性转变。gydF4y2Ba象J博士。gydF4y2Ba;2005年3月)。gydF4y2Ba

回到顶部gydF4y2Ba

作者gydF4y2Ba

蒂姆•哈里斯gydF4y2Ba微软研究院(tharris@microsoft.com)gydF4y2Ba

西蒙•马洛gydF4y2Ba微软研究院(simonmar@microsoft.com)gydF4y2Ba

西蒙•佩顿琼斯gydF4y2Ba微软研究院(sirnonpj@rnicrosoft.com)gydF4y2Ba

莫里斯病gydF4y2Ba布朗大学(mph@cs.brown.edu)gydF4y2Ba

回到顶部gydF4y2Ba

脚注gydF4y2Ba

DOI: http://doi.acm.org/10.1145/1378704.1378725gydF4y2Ba

回到顶部gydF4y2Ba

数据gydF4y2Ba

F1gydF4y2Ba图1。STM接口。gydF4y2Ba

回到顶部gydF4y2Ba


©2008 acm 0001-0782/08/0800 $5.00gydF4y2Ba

允许为个人或课堂使用本作品的全部或部分制作数字或硬拷贝,但不得为盈利或商业利益而复制或分发,且副本在首页上附有本通知和完整的引用。以其他方式复制、重新发布、在服务器上发布或重新分发到列表,需要事先获得特定的许可和/或付费。gydF4y2Ba

数字图书馆是由计算机协会出版的。版权所有©2008 ACM, Inc.gydF4y2Ba


没有发现记录gydF4y2Ba

Baidu
map