有些论文可能真的很难阅读,但有两篇非常清晰简洁:
Transactional Locking II,Dave Dice,Ori Shalev,Nir Shavit,它用任何语言描述了“TL2”STM 算法。
Deuce:Java 中的非侵入性软件事务内存,Guy Korland,Nir Shavit,Pascal Felber,解释了加载时间类转换器,它将常规 Java 类转换为内存中的类,这些类具有额外的字节码来执行 STM。这与该问题相关,因为该论文解释了如何将没有 STM 的代码机械地转换为在任何 OO 语言中执行 STM 的代码。
Deuce 框架允许您插入您希望使用的实际算法;包括TL2算法。由于 Deuce 框架是 Java,以下讨论使用 Java 术语,但仅假设您使用 OO 语言编写。
下面将概述 TL2 方法。这些论文有替代方法的链接,但研究一种算法可以回答很多问题。
你如何实现STM?对我来说神秘的部分是,给定一些任意代码块,您需要一种方法来返回并确定每个步骤中使用的值是否有效。
关于 TL2 如何处理 STM 的一个简短答案是“簿记”,然后仅在提交时使用写锁。阅读论文以获取详细信息,但板刷轮廓如下。您可以在原始代码中使用的每个属性都有一个 getter 和 setter。在转换后的代码中,还会有属性的版本号和添加到 getter 和 setter 的附加代码。您需要将您在事务中读取的每个属性的版本记录为事务“读取集”。您可以通过让每个 getter 将看到的属性版本添加到 threadlocal 链表中来做到这一点。您还需要将写入缓冲为线程本地中的“写入集”,直到您提交为止。请注意,getter 方法需要检查并返回给定字段的线程本地写入集值(如果有的话)。
在提交时,您对您将要写入的每个属性进行写入锁定。虽然您有锁,但您会仔细检查您的读取集是否仍然有效;您在事务中读取的属性没有被另一个事务更新到更高版本。如果是这样,那么您的业务逻辑可能无效,因为您可能会有不一致的读取,因此您需要回滚整个事务。如果最终检查通过,那么你通过刷新你的写集提交,碰撞这些属性的版本,释放写锁,最后清除写集和读集线程本地列表。
该论文解释说,如果算法检测到自 tx 开始以来已写入正在读取的属性,则该算法可以提前中止。该论文有一些巧妙的技巧来加速只读事务。它甚至有一个技巧来确定哪些块是只读的,哪些是读写的。任何对此类事物感兴趣的人都应该喜欢这两篇论文。
上面论文中的 Deuce 框架展示了如何通过在加载时将新的 java 字节码注入到类中来更改所有的 getter 和 setter。其他语言可能有一个特殊的编译器或预处理器,它们将普通代码执行相同的机械转换为支持 STM 的代码。特别是使用 Deuce,您的源代码对象可以具有简单的 getter setter 对,但在运行时转换的类具有丰富的 getter setter 来完成书本工作。
将常规代码转换为 STM 代码(尤其是在运行时)很有趣,但如果您需要实际编写启用 STM 的数据结构,则不需要任何魔法酱。相反,只需使用 a和 a创建一个Ref
类,并使数据结构对象之间的每一个关系都由句柄组成。在你的课堂上,你可以做线程本地的书本。然后,您可以实现一个简单版本的“TL2”或任何其他适用于您的数据结构和读写并发的算法。 get()
set(x)
Ref
get
set
Ref
这似乎也表明,就像任何其他“锁定”解决方案一样,您希望您的关键部分尽可能小(以减少冲突的可能性),对吗?
TL2 有一个关键时期是持有写锁,然后进行最终检查和写入,这很容易理解和优化,无需了解应用程序业务逻辑。如果您为每个数字分配一个唯一属性,您可以通过按升序获取锁来轻松避免死锁。重要的是要注意,假设提交检查将通过,您的所有业务逻辑都是推测性地完成的。在执行任意缓慢的业务逻辑时,您不会持有锁。您可以进行多个 Web 服务查找或缓慢的数据库调用,并且在提交之前您不会使用任何锁。显然,专业人士将调整通用关键部分。
该论文清楚地表明,该算法可能会比特定业务逻辑所需的更频繁地中止。通用算法不知道特定的脏读是否不会影响实际的写入结果。当给定的脏读集不需要回滚时,理解实际业务逻辑的手写逻辑可以知道特殊情况。但是,如果您有大量代码要编写,并且应用程序回滚的可能性非常低,那么通用的机械 STM 方法可能会导致更少的错误并表现良好。
此外,STM 是否可以简单地检测“另一个线程在执行计算时进入该区域,因此计算无效”或者它是否可以实际检测是否使用了破坏值(因此幸运的是,有时两个线程可能同时执行相同的关键部分而没有需要回滚)?
TL2 方法是关于读取或写入的数据,而不是关于执行它的代码。这是你得到和设置的,也是最重要的;以及在刷新所有写入之前是否有任何其他线程踩到您的脚趾。代码所需要的只是您在业务逻辑中有一个begin()
,来启动、结束commit()
和rollback()
中止事务。即使这样也可以生成代码。使用Java,您可以在方法上使用@Transactional 注释标记您的方法,然后生成将您的方法调用包装在try/catch/finally 中的代码,该代码将开始/提交/回滚作为惯用的java。Deuce 在类加载时注入这样的逻辑。
再一次,您不需要这样的魔法酱来在您自己的启用 STM 的数据结构中开始/提交/回滚。您可以明确地将所有内容放入您的数据结构逻辑代码中,以使用任何 OO 语言创建您自己的明确启用 STM 的类。