例如,考虑这个函数,它可以在 WM 中使用,允许在给定的显示器上将窗口从一个桌面移动到另一个桌面,
moveWindowSTM :: Display -> Window -> Desktop -> Desktop -> STM ()
moveWindowSTM disp win a b = do
wa <- readTVar ma
wb <- readTVar mb
writeTVar ma (Set.delete win wa)
writeTVar mb (Set.insert win wb)
where
ma = disp ! a
mb = disp ! b
显然还有它的IO
包装,
moveWindow :: Display -> Window -> Desktop -> Desktop -> IO ()
moveWindow disp win a b = atomically $ moveWindowSTM disp win a b
然后假设
- 我们的交易在第三次尝试时成功了,
ma
因为在第一次尝试时,它会因另一个并发事务提交而失效,该事务在我们的事务之后立即更改了内部的值wa <- readTVar ma
,- 而在第二次尝试时,它被另一个并发事务无效,该事务开始提交并更改了我们的事务中的一个或
ma
两个的内容。mb
writeTVar ma (Set.delete win wa)
在这种情况下,交易日志将如何演变,以及验证在什么时候会失败?
Simon Marlow 所著《Haskell 中的并行和并发编程》的相关摘录如下(但 Simon Peyton Jones 所著的论文《美丽的并发性》中也提供了类似的信息):
STM 事务的工作原理是累积事务期间迄今为止发生的操作的日志。日志有三种
readTVar
用途:writeTVar
通过将
writeTVar
操作存储在日志中而不是立即将其应用到主内存中,丢弃事务的影响很容易;我们只需丢弃日志即可。因此,中止事务的代价很小且固定。每个人都
readTVar
必须遍历日志来检查 是否是TVar
由之前的 写入的writeTVar
。因此,readTVar
是日志长度上的 O(n) 操作。因为日志包含所有操作的记录
readTVar
,所以可以使用它来发现事务期间的完整TVars
读取集,我们需要知道这些才能实现retry
。当事务结束时,STM 实现会将日志与内存内容进行比较。如果当前内存内容与 读取的值匹配
readTVar
,则将事务的结果提交到内存;如果不匹配,则丢弃日志并从头开始重新运行事务。此过程以原子方式进行,方法是在TVars
事务执行期间锁定所有涉及的 。GHC 中的 STM 实现不使用全局锁;TVars
提交期间仅锁定涉及事务的 ,因此对不相交的 集合进行操作的事务TVars
可以不受干扰地继续进行。