在STM上阅读Bartosz Milewski出色的blog post时,我很高兴阅读以下内容:
但是,据我了解,这种行为不是自动的,是吗?如果我使用TVar (Map k a)
,它将不会充当整个 map 的单个全局锁?为了获得这种细粒度行为的好处,我(或某人)将必须实现内部包含TMap
的 map 替换(例如TVars
),对吗?
这似乎是一个显而易见的问题,但是在阅读STM实现时,我对TVar
的读取和内存位置的读取感到困惑。我只想确保我做对了!
Bartosz进一步说:
据我了解,与STM的区别在于,尽管STM实现实际上使用锁来实现手动锁定解决方案的方式,但锁的实际获取和释放是由运行时处理的,而不是由程序员处理的-对吗?
最佳答案
TVar
是可变单元格。对于不可变的结构,没有两个线程可以来回传递修改,因此我们需要一些可变单元的概念来产生效果。特别是,我们有
writeTVar :: TVar a -> a -> STM ()
这会创建一个
SMT
Action 来替换可变单元格中的值。我们可以将其中一些操作排序在一起,构建一个更大,更复杂的STM
操作,然后调用atomically :: STM a -> IO a
一次原子地提交整个
STM
Action 。这是软件事务存储的“事务”部分:具有自己对这些可变单元的引用的其他线程将仅见证atomically
执行的STM
操作的全部,而没有子部分。为了实现此目的,Haskell可以使用锁定或更巧妙的方法-这仅仅是实现细节。 STM
唯一让您意识到的是STM
块中的 Action 可以根据需要重复运行-因此,禁止修改某些共享存储单元之外的副作用。那么,如何实现细粒度的并发呢?轻松:我们仅提供更多可变的单元格即可用于各种线程进行同步。例如,我们可以读取至少3种不同的
Map
类型。TVar (Map k v)
Map k (TVar v)
TVar (Map k (TVar v))
第一个允许并发线程同时对整个
Map
进行修改,以使部分更改不可见。第二个是允许对任何存储值进行更改,但要保持映射本身的结构(键的选择和存储值的选择)是不可变的,并且更改不能轻易传播到其他线程。最终选择
TVar (Map k (TVar v))
是最灵活的。我们可以通过在外部Map
上进行同步来对TVar
进行全面修改,并且可以通过读取value-TVar
并在其中的操作进行同步来对存储在 map 中的值进行更改。可用于此类树的全套可能语义是多种多样的,允许“整个Map
锁定”和“单个值锁定”同时发生。关于haskell - STM是否为现有数据结构提供细粒度的锁定?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22801131/