理论教育 STM:保护共享数据,提升性能

STM:保护共享数据,提升性能

时间:2023-11-24 理论教育 版权反馈
【摘要】:必须提供某种手段去阻止这种事情的发生,STM正是为此而生,它会保护共享数据,并提供与传统的锁机制不同的实现,以提升性能。例11-3是Scala中创建临界区的例子。STM包装共享变量。在STM中使用“乐观锁”实现了ACID的前3个特性,没有实现“持久化”特性,因为STM都发生在内存中,内存中的事务永远都不会持久化。

STM:保护共享数据,提升性能

STM(Software Transactional Memory)软件事务内存,是一种多线程之间数据共享的同步机制。对于并行计算编程而言,只要将线程中需要访问共享内存关键逻辑部分划分出来封装到一个事务中即可,编程人员不再需要关心相关的同步一致性问题,全部交由事务内存系统来出处理。事务内存系统要求保证操作的原子性和独立性。原子性要求事务必须被完整执行或不被执行,独立性则要求在事务递交前,外部不能得知事务内部的状态,即中间不稳定态。

这里举一个事务应用的现实情景,例如有一个售票网站,正好有两个人在买票,当第一个人选中某个座位,在下单买走它之前,另外一个人正好也选中了这个座位,那么问题就发生了,同一位置的票被卖了两次,显然在现实生活中这是不容许发生的,因为这产生了冲突。如何解决这种冲突呢?必须提供某种手段去阻止这种事情的发生,STM正是为此而生,它会保护共享数据,并提供与传统的锁机制不同的实现,以提升性能。

传统的保护共享数据的方法是,当一个线程去访问共享数据的时候,会阻塞其它所有欲访问该数据的线程进入临界区,这种机制被称为“锁”。传统的这种“锁”机制,在Java或Scala语言中以synchronized同步代码块的形式来表现。例11-3是Scala中创建临界区的例子。

【例11-3】Scala中创建synchronized同步代码块示例。

Synchronized保证同时只有一个线程进入临界区执行。当一个线程获得了seats上面的锁之后,只有这个线程可以进入seats的synchronized块中执行,执行完毕之后会释放seats上面的锁,之后其他线程才能够获得seats上的锁并进入synchronized中执行。因此所有的线程在synchronized块上面是依次串行执行的,保证了在同一时刻只有一个线程访问共享变量,确保了共享变量的一致性。但是这种锁机制也存在一个问题,如果线程只是想去读共享变量,当遇到synchronized的时候也必须要等待,这就降低了系统的整体性能,并且在大多数时候没有线程使用共享变量也会产生锁,这样的锁称为“悲观锁”,因为其假设在任何时候都可能会有线程访问并修改共享变量。与“悲观锁”对应的是“乐观锁”,其认为在访问和修改共享变量的时候不会产生任何问题,因此执行代码的时候不会有任何锁。在“乐观锁”的实现中,当线程离开临界区域的时候,系统会检测可能的更新冲突,如果这里没有更新冲突,那么直接提交事务,如果检测到有冲突发生,那么所有的改变都会回滚并尝试重新执行临界区代码。

在STM中使用的就是“乐观锁”,STM能防止因多线程访问共享变量造成的数据不一致性问题,其关键就是要知道共享数据在事务中是否已经被改变,为了检测这种改变,Ak⁃ka中的做法是将共享变量包装到STM的引用中,如例11-4所示。

【例11-4】STM包装共享变量。(www.daowen.com)

现在要想获得或者更新共享变量,只需要简单地使用seats()即可。更新seats如例11-5所示。

【例11-5】更新共享变量示例。

上面处于Ref包装中的seats变量,只能够在atomic块中使用(这是Scala实现STM的语法规定),在atomic块中写的代码将被视为一个原子命令被执行,并且在编译的时候atomic里需要一个隐式变量来为Ref中的冲突做检测。如例11-6所示为在atomic块中使用Ref变量。

【例11-6】在atomic块中使用Ref变量示例。

上面的代码跟synchronized做的是同样的事情,但是锁工作的机制完全不相同,因为STM使用的是“乐观锁”,而synchronized使用的是“悲观锁”。使用synchronized临界区只会执行一次,但是使用STM的atomic块执行相同的逻辑,临界区代码可能会执行多次,这是因为在atomic块执行完成之后,有一个检查操作将会执行,这个操作会去检查是否有冲突发生。

在STM中使用“乐观锁”实现了ACID的前3个特性,没有实现“持久化”特性,因为STM都发生在内存中,内存中的事务永远都不会持久化。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈