【问题标题】:Does STM provide fine-grained locking for existing data structures?STM 是否为现有数据结构提供细粒度锁定?
【发布时间】:2014-04-02 03:42:48
【问题描述】:

在 STM 上阅读 Bartosz Milewski 的精彩 blog post,我很高兴看到以下内容:

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

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

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

Bartosz 进一步说:

每个节点的手动锁定很难正确实现,因为 死锁的风险。

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

【问题讨论】:

  • 这取决于实现,但有许多无锁方式来实现 STM(例如原子比较和交换)。至于你的第一个问题,是的,你需要TVars 的地图,而不是TVar (Map k v)
  • @ThomasM.DuBuisson 对此表示感谢。你的意思是我实际上可以有一个细粒度的Map,还是我需要完全重新实现Map才能在内部使用TVars?
  • 另外,我刚刚了解到“获取”实际上是一个词。幸运的猜测。
  • 您可以简单地拥有一个值为TVars 的法线贴图。更具体地说,Map k (TVar v).

标签: haskell stm


【解决方案1】:

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

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)) 是最灵活的。我们可以通过在外部TVar 上同步来对Map 进行大规模修改,我们可以通过读取值-TVars 并同步其中的操作来更改存储在映射中的值。可用于此类树的全套可能语义有无数种,允许“整个Map 锁定”和“单个值锁定”同时发生。

【讨论】:

  • 绝妙的答案!感谢您的解释。所以为了清楚起见,即使TVar (Map k (TVar a)) 也无法实现 Bartosz 描述的细粒度锁定 - 这需要实现一个全新的 TMap 数据结构,它将 TVars 放在树的内部节点周围(如果这是确实是使用的存储结构),而不仅仅是叶子。
  • 是的,您的粒度与您的 TVar 的位置完全相同。
猜你喜欢
  • 2012-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-18
  • 2015-02-02
  • 1970-01-01
相关资源
最近更新 更多