Transactional Memory
这个概念是从数据库中借来的。一个Transaction应该具有ACID的特性。
随着处理器发展的趋势,高质量的并发程序越来越重要。目前,编译器可以自动并行化程序的情况还比较有限,大量的并发程序是由程序员写出来的。对于并发程序中共享资源的控制,基本上都是基于锁机制的。对于这样的并发程序,开发的难度是很大的。普遍认为,开发并发程序的工作量是同样的单线程程序的三倍。
而Transactional Memory则提出了另外一种解决问题的方法。
其基本的想法是:程序员可以指定某一段程序(可以包含嵌套的函数调用)对Memory的操作为一个Transaction,这段程序在执行的时候,在别的部分看来,它满足数据库中Transaction的A(原子)C(并发)I(独立)的性质(不包括性质D(永久))。更详细一点说,如果atomic关键字用于指定Transaction,那么
atomic{
op1;
op2;
op3;
...
}
代码段执行起来就像一个原子:好像整个系统中只有一个线程在执行它,并且从头执行到尾。对于TM的实现,一般采用的是“乐观的同步”思想。当一个Transaction可以执行的时候,就执行它。而且在这里并不会去锁住某个锁。但是,执行这个Transaction的线程必须有一个线程局部的log,对于Transaction中访问到的每一个“共享”变量,在第一次访问时需要记下它的原始值,之后,记录下所有对它的读和写操作。对共享变量的写操作并不会真的去写变量,而只是记录在log中。当Transaction执行结束时,需要进行提交。这是,运行时系统再次去检查共享变量的值,如果所有的共享变量的当前值和log中记录的原始值一样(没有被别的线程修改过),则Transaction成功提交,将log中的对共享变量的最终修改写到共享变量中。否则,提交失败,运行时系统自动地清除空该Transaction的log,然后重新执行这个Transaction。
单从接口而言,TM的方法比加锁的方法更容易使用。此时,程序员不需要自己去指定程序如何并行,而只需要指定一些必须原子完成的代码段,并行性由运行时系统自动的发现并加以利用(运行时系统可以根据数据依赖来决定某个Transaction是否可以运行,并为每一个可以运行的Transaction分配一个单独的线程)。
事实上,Transactional Memory还是有些问题的。第一,对于一般常用的命令式的语言,编译器很难自动的,准确的分析出那些变量是“共享”变量,那些变量是线程私有的变量。为了保证正确性,只好做一些保守的假设,于是,在一个Transaction的执行过程中会有大量的读写内存的操作被记录在log中,导致的效率的低下。
对于这个问题,有两种解决方法。第一,用硬件实现Transactional Memory的支持,使得log是自动记录的。
而我认为更好的另外一个方法是,使用类似于Haskell的纯粹的函数式语言。对于纯粹的函数式语言,计算是没有副作用的。而用于在线程间交换数据或记录状态的“可变”实体,都被显式地封装在Monad中了,所以,很容易从一个Transaction中分辨出这些Monad。那么在记录log的时候,只要关注这少量的几个变量就可以了,其他的都是无副作用的函数,无论怎么计算,得到的结果都会相同。这样,就可以使用软件高效的实现Transactional Memory。
在并发程序设计越来越重要的现在,程序员的工具仍然是Lock或是Message之类的东西,外加上一个线程库。当然,有很多人致力于怎么定义更好的接口,开发更方便的辅助工具,但是,我们仍然没有一套像“结构化程序设计”或是“面向对象程序设计”那样的方法学来进行并发程序的设计。也许Transactional Memory仍然不会很理想,但是至少是“无穷黑暗中的一丝曙光”......
推荐文章“Composable Memory Transactions”
阅读(2523) | 评论(3) | 转发(1) |