事务内存是一种并行程序设计的方式,其来自于数据库管理系统(DBMS)中的事务(Transaction)概念。事务内存目前有两种实现方式,基于软件的STM(Software Transactional Memory)和基于硬件的HTM(Hardware Transacational Memory)。
虽然事物内存的事务编程概念来自于数据库的事务,但在概念上还是有很大的区别的。
1 数据库的 数据是存储在磁盘上的,而在事物内存技术中数据是存放在内存的。
2 事物内存技术中事务不满足acid 中的d(durable) 性质,因为它只在内存中有效。
3 事物内存技术中事 务相对小的多。
最近,微软发布了.NET 4.0 Beta 1的一个单独的新版本,其中包含了STM.NET,也就是Software Transactional Memory。通常我们使用基于锁的同步机制来控制对共享内存的访问,STM则是锁之外的另一种选择。
微软将STM.NET定义为:Software Transactional Memory(STM.NET)是一种可以高效隔离共享状态的机制。开发人员可以将一段代码标记为“原子”事务,并与其它并行运行的事务代码“隔离”开来。
事务内存(Transactional memory)在学术界被认为是一项有前途的技术,并且在当前利用多核硬件提高程序扩展性的大潮中,也作为一项受欢迎的技术被反复提及,其目的是使应用程 序开发人员可以利用STM的并发功能,将那些由专家开发的组件组合在一起,事务内存使这变得简单而安全。
软件事务内存 (STM) 是一种模拟数据库事务的并发控制 机制来控制在并行计算时对共享内存的访问控制。它是锁的一种替代机制。在STM中,一个事务指的是一段读、写共享内存的代码。这些读写操作在逻辑上是一个独立的单元,其中间状态对于其它的事务而言,是不可见的。
性能
与现在许多多线程应用程序广泛使用的锁机制不同,STM是一种非常乐观的并发控制机制:一个线程独立完成对共享内存的修改,完全忽略可能会有其它的线程存在,但是线程在日志中记录对共享内容的每一个读写动作。其他的并发控制一般是在进行写操作时来保证与其他事务的一致性(不能修改已经被别的失误修改过的共享数据),STM在完成一个事务之后,再验证其它线程没有并发的对共享内存进行或修改,从而保证事务是完整的。因此,STM事务的最后一个操作是验证,如果验证通过,则提交,否则取消,导致所有以前进行的修改动作回滚。如果一个事务不能够提交,一般的,事务将回顾,并且从入口开始重新执行。
采用这种乐观策略可以增加并发性能:任何线程无需等待一个资源,多个线程可以并行而且安全的修改同一数据结构的多个部分(如果采用锁,它们一般在同一个锁的保护之下)。除了在事务失败后需要重试而增加开销之外,在现实世界中,由于冲突是很罕见的,因此,实际上可以带来性能的提升。尤其是在多处理器的情况下。
不过,在一些实践中,在较少CPU(1-4)的系统上,基于STM的应用程序相对于一些精心调节的基于Lock的应用程序而言,会有一定的性能损失。主要的原因是在STM事务中,需要维持log,以及额外的花在提交事务上的时间。不过,即使在这种情况下,性能也不会低于50%。 [1]? 相对而言,STM拥护者认为STM带来的优势更为明显。
理论上,在最糟糕的情况下,当 n 个并发事务同时运行,他们需要 n 倍的内存和处理器,实际的需要取决于具体的实现细节(比如说一个事务可以尽早的失败以避免后续额外的开销)。在某些应用场景下,基于Lock机制的算法会比基于STM机制的算法更好。
STM.NET的优点与缺点
除了性能上的优势之外,STM可以在概念上简化多线程应用的模型,使得程序与一些已知的抽象概念如对象、模块等相处更为融洽,从而变得更易为维护。基于Lock的软件开发在实践上存在如下的问题:
• 需要将一些相互交错、局部的操作理解成为一系列的独立的、关系不密切的代码,这对程序员而言,是一个艰巨而且很容易犯错的任务。
• 需要程序员采用一定的策略来避免死锁、活锁,以及其他可能的错误。这些策略是强制的,且很容易犯错的,当出现相关的问题时,难以重现、排错。
• 会导致优先级冲突。经常会出现一个高优先级的线程需要等待另外一个低优先级的线程。
相反的,STM的概念要简单得多,因为每一个事务可以理解为隔离的,就像只有单个线程进行操作一般。死锁等会完全避免,无需程序员担心这个问题。优先级冲突的问题仍然存在,但一个高优先级的事务可以令得其他低优先级的事务提前终止。
相应的,由于需要提前结束事务,也对STM的事务提出了一些限制:在事务中不能够操作那些不能回滚的动作,例如大部分的IO操作。这些限制在实际开发中是通过创建缓冲区,记录这些不可回滚的操作,然后在事务完成后,在事务之外一次性的执行。在Haskell语言中,编译器会利用类型信息,做这个强制性检查。
STM.NET使用Atomic.Do()将一段代码标记为一个事务:
Atomic.Do(()=> {
});
STM与锁机制一样会导致系统性能降低,这是因为它需要维护读写日志,而且提交时会花费额外的时间。一些人认为STM比锁更容易使用,因此在易用性上的优势足以抵消性能上的降低。在一项名为《事务编程真的容易么?》的研究中,来自奥斯丁德克萨斯大学的Christopher J. Rossbach、Owen S. Hofmann和 Emmett Witchel比较了STM与锁的学习和开发过程:
一般来说,事务内存比简单的锁需要更多的开发时间,但少于细致的锁和条件同步。
……我们发现在更复杂的情况下,简单的锁和事务所用的时间要少于细致的锁。这体现了事务的初衷,在需要多个锁的情况下,减少编码、调试和锁顺序的复杂性。
这项研究总结道:
这证明了即使新手在理解事务方面可能存在困难,但事务编程比高性能的锁更安全。对学生的主观评测显示,他们认为事务内存比简单的锁要难一些,但是比细致的 锁和条件同步简单。然而在学生写的代码中,同步错误的比率呈现出戏剧化的结果。对于相似的编程问题,使用事务比使用锁更容易得到正确的结果。
系统配置需求:Visual Studio 2008,Windows Installer 3.1以上, Internet Explorer 5.01以上。目前STM.NET只支持C#。
【编辑推荐】