使用简单IORef实现Haskell并发?

使用简单IORef实现Haskell并发?,haskell,concurrency,shared-state,ioref,Haskell,Concurrency,Shared State,Ioref,我一直在问一些关于Haskell中并发性的问题,特别是TVar,我对TVar的livelock感到担忧 相反,我提出了这个解决方案 (1) 将程序中的所有共享数据包装在一个数据结构中,并将其包装在IORef中。 (2) 只需使用atomicModifyIORef进行任何更改即可 我相信这可以防止死锁和活锁(而TVar只能防止前者)。此外,由于原子修改ioref只是将另一个thunk链接到一个链中(这是一对指针操作),因此这不是一个瓶颈对数据的所有实际操作都可以并行完成,只要它们不相互依赖。Has

我一直在问一些关于Haskell中并发性的问题,特别是
TVar
,我对
TVar
的livelock感到担忧

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

(1) 将程序中的所有共享数据包装在一个数据结构中,并将其包装在
IORef
中。 (2) 只需使用
atomicModifyIORef
进行任何更改即可


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


但是我觉得这太简单了。我有没有漏掉什么“gotchas”?

好吧,这篇文章写得不好。通过一个IORef序列化所有共享内存修改意味着一次只有一个线程能够修改共享内存,而您真正做的只是创建一个全局锁。是的,它可以工作,但速度很慢,远没有TVAR甚至MVAR那么灵活。

好吧,它的构图不好。通过一个IORef序列化所有共享内存修改意味着一次只有一个线程能够修改共享内存,而您真正做的只是创建一个全局锁。是的,它可以工作,但速度很慢,远不如TVars甚至MVars灵活。

AFAICT如果你的计算在处理
IORef
内容后留下未计算的thunk,那么thunk将在任何试图使用结果的线程中进行计算,而不是像你想的那样被并行评估。请参阅
MVar
docs中的gotchas部分


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

AFAICT如果您的计算在处理
IORef
内容后留下未经评估的thunks,该thunk将只在任何线程尝试使用结果时进行计算,而不是像您希望的那样并行计算。请参阅
MVar
docs中的gotchas部分


如果您提供了一个您试图解决的具体问题(或一个简化但类似的问题),可能会对其他人更有趣、更有用。

如果满足以下条件,则此设计可能是可行的:

  • 读将比写更普遍
  • 写操作之间会有大量的读操作
  • (可能)写入只会影响全局数据结构的一小部分
当然,考虑到这些条件,几乎任何并发系统都可以。因为您关心的是livelock,所以我怀疑您正在处理更复杂的访问模式。在这种情况下,请继续阅读

您的设计似乎遵循以下推理链:

  • atomicModifyIORef
    非常便宜,因为它只创建Thunk

  • 因为
    atomicModifyIORef
    便宜,所以不会引起线程争用

  • 廉价数据访问+无争用=并发FTW

  • 这里是这个推理中缺少的一步:您的
    IORef
    修改只创建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)
    
    以下是使用这些函数作为参数时发生的情况:


  • !()如果符合以下条件,这种设计可能是可以的:

    • 读将比写更普遍
    • 写操作之间会有大量的读操作
    • (可能)写入只会影响全局数据结构的一小部分
    当然,考虑到这些条件,几乎任何并发系统都可以。因为您关心的是livelock,所以我怀疑您正在处理更复杂的访问模式。在这种情况下,请继续阅读

    您的设计似乎遵循以下推理链:

  • atomicModifyIORef
    非常便宜,因为它只创建Thunk

  • 因为
    atomicModifyIORef
    便宜,所以不会引起线程争用

  • 廉价数据访问+无争用=并发FTW

  • 这里是这个推理中缺少的一步:您的
    IORef
    修改只创建thunk,您无法控制thunk的计算位置。如果无法控制数据的计算位置,那么就没有真正的并行性

    由于您还没有给出预期的数据访问模式,这只是猜测,但是我预计接下来会发生的事情是,您对数据的重复修改将建立一个恶作剧链。然后,在某个时刻,您将读取数据并强制进行计算,从而使所有这些thunk在一个线程中按顺序进行计算。此时,您可能已经编写了single threade