1

我一直在问一些关于 Haskell 并发的问题,特别TVarTVar.

相反,我提出了这个解决方案。

(1) 将程序中的所有共享数据包装在一个数据结构中,并将其包装在一个IORef. (2) 只需使用atomicModifyIORef.

我相信这可以防止死锁和活锁(而 TVar 只能防止前者)。此外,因为atomicModifyIORef只是将另一个 thunk 链接到一个链中(这是几个指针操作),所以这不是瓶颈。对数据的所有实际操作都可以并行完成,只要它们不相互依赖即可。Haskell 运行时系统会解决这个问题。

然而我觉得这太简单了。有没有我错过的“陷阱”?

4

3 回答 3

7

如果以下情况属实,则此设计可能没问题:

  • 读取将比写入更普遍
  • 写入之间会穿插多次读取
  • (可能)写入只会影响全局数据结构的一小部分

当然,考虑到这些条件,几乎任何并发系统都可以。由于您担心活锁,我怀疑您正在处理更复杂的访问模式。在这种情况下,请继续阅读。

您的设计似乎受到以下推理链的指导:

  1. atomicModifyIORef非常便宜,因为它只会产生 thunk

  2. 因为atomicModifyIORef便宜,不会引起线程争用

  3. 廉价数据访问 + 无争用 = 并发 FTW!

这是此推理中缺少的步骤:您的IORef修改只会创建 thunk,并且您无法控制评估 thunk 的位置。如果您无法控制评估数据的位置,那么您就没有真正的并行性。

由于您尚未提出预期的数据访问模式,这是推测,但是我预计会发生的情况是您对数据的重复修改将建立一个 thunk 链。然后在某个时候,您将从数据中读取并强制进行评估,从而导致所有这些 thunk 在一个线程中按顺序进行评估。此时,您可能已经编写了单线程代码。

解决此问题的方法是确保在将数据写入 IORef 之前对数据进行评估(至少在您希望的范围内)。这就是返回参数的atomicModifyIORef用途。

考虑这些功能,旨在修改aVar :: IORef [Int]

doubleList1 :: [Int] -> ([Int],())
doubleList1 xs = (map (*2) xs, ())

doubleList2 :: [Int] -> ([Int], [Int])
doubleList2 xs = let ys = map (*2) xs in (ys,ys)

doubleList3 :: [Int] -> ([Int], Int)
doubleList3 xs = let ys = map (*2) xs in (ys, sum ys)

当您将这些函数用作参数时,会发生以下情况:

  1. !() <- atomicModifyIORef aVar doubleList1- 只创建一个 thunk,不评估数据。对于从下一个阅读的任何线程来说,这是一个令人不快的惊喜aVar

  2. !oList <- atomicModifyIORef aVar doubleList2- 仅评估新列表以确定初始构造函数,即(:)or []。仍然没有做任何真正的工作。

  3. !oSum <- atomicModifyIORef aVar doubleList3- 通过评估列表的总和,这保证了计算得到充分评估。

在前两种情况下,要做的工作很少,所以atomicModifyIORef会很快退出。 但这项工作并没有在那个线程中完成,现在你不知道它什么时候会发生。

在第三种情况下,您知道工作是在预期的线程中完成的。首先创建一个 thunk 并更新 IORef,然后线程开始计算总和并最终返回结果。但是假设其他线程在计算总和时读取数据。它可能会开始评估 thunk 本身,现在你有两个线程在做重复的工作。

简而言之,这种设计并没有解决任何问题。它可能在您的并发问题并不困难的情况下工作,但对于您一直在考虑的极端情况,您仍然会在多个线程执行重复工作的情况下燃烧循环。与 STM 不同,您无法控制重试的方式和时间。至少 STM 你可以在事务中间中止,通过 thunk 评估它完全不在你的掌控之中。

于 2012-04-12T10:11:58.320 回答
5

好吧,它不会很好地组成。通过单个 IORef 序列化所有共享内存修改将意味着一次只有一个线程能够修改共享内存,您真正所做的只是创建一个全局锁。是的,它会起作用,但它会很慢,而且远不及 TVar 甚至 MVar 那样灵活。

于 2012-04-12T00:19:17.563 回答
1

AFAICT 如果您的计算在对IORef内容进行处理后留下未评估的 thunk,则该 thunk 将简单地在任何尝试使用结果的线程中进行评估,而不是按照您的意愿并行评估。MVar请参阅文档的陷阱部分, here

如果您提供了一个您正在尝试解决的具体问题(或一个简化但类似的问题),它可能对其他人更有趣和更有帮助。

于 2012-04-12T02:11:49.267 回答