阅读Bartosz Milewski 关于STM 的精彩博文,我很高兴看到以下内容:
但考虑到一个重要的事实:STM非常精细.例如,当您将项目插入树中时,STM事务将仅锁定您实际修改的节点.STM将轻松击败每个整棵树使用一个全局锁的解决方案.
但是,据我了解,这种行为不是自动的,是吗?如果我使用a TVar (Map k a),它不会在整个地图上作为单个全局锁吗?为了获得这种细粒度行为的好处,我(或某人)必须实现内部TMap包含的地图替换(例如)TVars,正确吗?
这似乎是一个显而易见的问题,但是阅读STM实现我在读取TVar内存位置和读取内存位置时感到困惑.我只是想确保我做对了!
巴托斯进一步说:
由于存在死锁风险,每节点手动锁定很难正确实现.
据我所知,与STM的不同之处在于,虽然STM实现实际上使用锁定手动锁定解决方案的方式,但锁的实际检查和释放是由运行时处理的,而不是程序员 - 正确吗?
A TVar是可变细胞.对于不可变结构,没有两个线程可以来回传输修改,因此我们需要一些可变单元的概念来产生效果.特别是,我们有
writeTVar :: TVar a -> a -> STM ()
Run Code Online (Sandbox Code Playgroud)
这会创建一个SMT替换可变单元格内部值的操作.我们可以将这些操作中的一些进行排序,构建更大,更复杂的STM操作,然后调用
atomically :: STM a -> IO a
Run Code Online (Sandbox Code Playgroud)
立即以原子方式提交整个STM动作.这是软件事务内存的"交易"部分:其它线程与这些可变的细胞自身的引用将只见证的整体atomically-performed STM动作,无子部分.要实现这一点,Haskell可能会使用锁定,或者更聪明的东西 - 它只是一个实现细节.STM您唯一要知道的是,您的STM块内的操作可能会在必要时重复运行 - 因此禁止修改某些共享内存单元之外的副作用.
那么我们如何实现细粒度并发呢?很容易:我们只为各种线程提供更多可变单元以进行同步.例如,我们可以阅读至少3种不同的Map类型.
TVar (Map k v)
Map k (TVar v)
TVar (Map k (TVar v))
Run Code Online (Sandbox Code Playgroud)
第一个将允许并发线程Map一次修改整个all,这样就不会看到部分更改.第二种,允许对任何存储值进行更改,但保持映射本身的结构 - 键的选择和存储值的选择 - 是不可变的,并且更改不能轻易传播到其他线程.
最后的选择,TVar (Map k (TVar v))是最灵活的.我们可以Map通过在外部进行同步来对其进行批量修改TVar,我们可以通过向下读取值TVar并同步其中的操作来更改存储在地图中的值.可用于这种树的全套可能语义无数允许"整体Map锁定"和"单个值锁定"一起发生.