Haskell:并发数据结构指南

我一直在试图理解并发性,而且我一直在努力找出更好的方法,一个大的IORef锁或者许多TVar 。 我已经达到了以下指导原则,对于这些是否大致正确或我是否错过了这一观点,我们将不胜感激。


让我们假设我们的并发数据结构是一个地图m ,像m[i]那样访问。 让我们也说我们有两个函数, f_easyf_hardf_easy很快, f_hard需要很长时间。 我们假设f_easy/f_hard的参数是m元素。

(1)如果您的交易看起来大致类似于m[f_easy(...)] = f_hard(...) ,请使用IORefatomicModifyIORef 。 懒惰将确保m仅在短时间内被锁定,因为它被更新为thunk。 计算索引可以有效地锁定结构(因为某些东西将会被更新,但我们现在还不知道),但是一旦知道了这个元素是什么,整个结构上的thunk只会移动到一个thunk上, ,然后只有那个特定的元素被“锁定”。

(2)如果您的交易看起来大致如此,那么使用大量的TVar s m[f_hard(...)] = f_easy(...) ,并且不会太多冲突。 在这种情况下使用IORef将有效地使应用程序成为单线程,因为无法同时计算两个索引(因为在整个结构中会存在未解决的问题)。 TVar让你同时计算出两个索引,但是,否定的是,如果两个并发事务都访问同一个元素,而其中一个是写入,那么一个事务必须被废弃,浪费时间(可能会有已在别处使用)。 如果这种情况发生了很多,你可能会更好地使用来自IORef锁(通过黑洞),但如果它不会发生太多的话,你将会与TVar更好地平行。

基本上在案例(2)中,使用IORef可以获得100%的效率(没有浪费的工作),但只使用1.1个线程,但对于TVar如果冲突数量少,则可以获得80%的效率,但使用10个线程,即使浪费了工作,结果仍然快7倍。


您的指导方针与[1](第6节)的结果有些类似,其中分析了Haskell STM的性能:

“特别是,对于在事务内部没有做很多工作的程序来说,提交开销似乎非常高,为了进一步观察这种开销,需要对提交时间过程的性能进行分析 - 粒度和细粒度STM锁定机制“。

我使用atomicModifyIORefMVar当我需要的所有同步都是简单的锁确保。 在查看对数据结构的并发访问时,它也取决于这个数据结构是如何实现的。 例如,如果您将数据存储在IORef Data.Map并经常执行读/写访问,那么我认为atmoicModifyIORef会降级为单线程性能,正如您猜测的那样,但对于TVar Data.Map也是如此。 。 我的观点是,使用适合并发编程的数据结构非常重要(平衡树不适用)。

这就是说,在我看来,使用STM的成功理由是可组合性:您可以将多个操作合并为单个事务,而不会令您头疼。 一般来说,这是不可能使用IORefMVar而不引入新的锁。

[1]软件事务内存(STM)的限制:在多核环境下解析Haskell STM应用程序。 http://dx.doi.org/10.1145/1366230.1366241

回复@克林顿的评论:

如果单个IORef包含所有数据,则可以使用atomicModifyIORef进行组合。 但是,如果您需要处理大量对该数据的并行读取/写入请求,则性能损失可能会变得很大,因为每对数据的并行读取/写入请求可能会导致冲突。

我会尝试的方法是使用数据结构,其中条目本身存储在TVar (将整个数据结构放入单个TVar )。 这应该会减少活锁的可能性,因为交易不会经常发生冲突。

当然,您仍然希望保持尽可能小的交易量,并且只有在确保一致性的绝对必要时才使用可组合性。 到目前为止,我还没有遇到过需要将多个插入/查找操作组合到单个事务中的场景。


除了性能之外,我还看到使用TVar的更根本原因 - 类型系统确保您不会执行任何“不安全”操作,如readIORefwriteIORef 。 数据共享是该类型的属性,而不是实现。 编辑: unsafePerformIO始终是不安全的。 如果您还在使用atomicModifyIORefreadIORef只是不安全的。 至少将你的IORef包装成新类型,并且只暴露一个包装的atomicModifyIORef

除此之外,不要使用IORef ,使用MVar或者TVar

  • 您描述的第一种使用模式可能没有很好的性能特征。 您可能最终(几乎)完全是单线程的 - 由于懒惰,每次更新共享状态时都不会有实际的工作发生,但是无论何时您需要使用这种共享状态,都需要强制整个积累的一堆thunk,并且具有线性数据依赖性结构。
  • 效率高达80%但并行性高得多,可以让您利用越来越多的内核。 在单线程代码中,您可以期待未来几年性能的最小提升。
  • 许多单词CAS可能以“硬件事务内存”的形式到达您附近的处理器,从而使STM更加高效。
  • 您的代码将更加模块化 - 如果在设计将所有共享状态置于单个引用后面时添加更多共享状态,则必须更改每段代码。 TVars和在较小程度上MVars支持自然模块化。
  • 链接地址: http://www.djcxy.com/p/63633.html

    上一篇: Haskell: Concurrent data structure guidelines

    下一篇: How can I get a strict accumArray?