5

阅读 Bartosz Milewski 在 STM 上的精彩博客文章,我很高兴阅读以下内容:

但是要考虑一个重要的事实:STM 是非常细粒度的。例如,当您将项目插入树中时,STM 事务只会锁定您实际修改的节点。STM 将轻松击败每棵树使用一个全局锁的解决方案。

但是,据我了解,这种行为不是自动的,是吗?如果我使用 a TVar (Map k a),它不会充当整个地图上的单个全局锁吗?为了获得这种细粒度行为的好处,我(或某人)必须实现一个内部TMap包含的地图替换(例如),对TVars吗?

这似乎是一个显而易见的问题,但在阅读 STM 实现时,我对TVars 的读取和内存位置的读取感到困惑。我只是想确保我做对了!

Bartosz 进一步说:

由于存在死锁的风险,每个节点的手动锁定很难正确实施。

据我了解,与 STM 的区别在于,虽然 STM 实现实际上使用了手动锁定解决方案可能使用的锁,但锁的实际获取和释放是由运行时处理的,而不是程序员 - 对吗?

4

1 回答 1

8

ATVar是一个可变单元格。对于不可变结构,没有两个线程可以来回传输修改,因此我们需要一些可变单元的概念来产生效果。特别是,我们有

writeTVar :: TVar a -> a -> STM ()

它创建了一个SMT替换可变单元格内的值的操作。我们可以将其中一些操作排序在一起,构建一个更大更复杂的STM操作,然后调用

atomically :: STM a -> IO a

一次原子地提交整个STM动作。这是软件事务内存的“事务”部分:具有自己对这些可变单元格的引用的其他线程只会见证整个atomically执行STM操作,没有子部分。为了实现这一点,Haskell 可能会使用锁定,或者更聪明的东西——它只是一个实现细节。唯一需要STM您注意的是,您的STM块中的操作可能会根据需要重复运行,因此禁止修改某些共享内存单元之外的副作用。

那么我们如何实现细粒度的并发呢?很容易:我们只是为不同的线程提供了更多的可变单元来同步。例如,我们可以读取至少 3 种不同的Map类型。

TVar (Map k v)
Map k (TVar v)
TVar (Map k (TVar v))

第一个将允许并发线程一次对整个进行修改,Map这样就不会看到部分更改。第二,允许对任何存储值进行更改,但保持映射本身的结构——键的选择和存储值的选择——是不可变的,并且更改不能轻易传播到其他线程。

最后的选择,TVar (Map k (TVar v))是最灵活的。我们可以Map通过在外部同步TVar来对 s 进行大规模修改,我们可以通过读取值并同步其中的操作来更改存储在映射中的值TVar。可用于这种树的全套可能语义是无数的,允许“整体Map锁定”和“单个值锁定”同时发生。

于 2014-04-02T07:05:34.070 回答