【问题标题】:How do laziness and parallelism coexist in Haskell?Haskell 中惰性和并行性如何共存?
【发布时间】:2019-12-18 15:31:53
【问题描述】:

人们认为 Haskell 在并行性方面具有优势,因为它具有不可变的数据结构。但是 Haskell 也很懒惰。这意味着数据实际上可以从 thunk 转变为评估结果。

所以看起来懒惰会损害不变性的优势。我错了还是 Haskell 有针对这个问题的对策?或者这是 Haskell 自己的特性?

【问题讨论】:

  • “数据实际上可以从 thunk 转变为评估结果”你能多说一下你在这里的意思以及为什么你相信这是真的吗?
  • @Thomas wiki.haskell.org/Thunk 我没有关于 haskell 实现的确切信息,但我认为这是实现惰性和 thunk 的最简单的解决方案。
  • 重点是,每个线程必须同步信息,无论 thunk 是否已经被评估。或者每个线程必须重新评估 thunk。 (我认为)
  • @Dai 因此,每次执行共享值评估时,线程都必须阻塞其他任务。

标签: haskell parallel-processing immutability lazy-evaluation


【解决方案1】:

是的,GHC 的 RTS 使用 thunk 来实现非严格评估,并且它们在后台使用了变异,因此它们需要一些同步。然而,由于大多数堆对象是不可变的并且函数是引用透明的,因此这被简化了。

在多线程程序中,thunk 的求值过程如下:

  • thunk 被原子地替换为BLACKHOLE 对象

  • 如果 same 线程在更新为 BLACKHOLE 后尝试强制 thunk,则表示无限循环,RTS 会抛出异常 (<<loop>>)

  • 如果 不同 线程尝试在它是 BLACKHOLE 时强制 thunk,它会阻塞,直到原始线程完成对 thunk 的评估并使用值更新它

  • 评估完成后,原始线程原子地用其结果替换thunk

例如,使用比较和交换 (CAS) 指令

因此这里存在潜在的竞争:如果两个线程试图同时强制执行相同的 thunk,它们可能都开始评估它。在这种情况下,它们会做一些多余的工作——但是,一个线程将成功地用结果覆盖BLACKHOLE,而另一个线程将简单地丢弃它计算的结果,因为它的 CAS 将失败。

安全代码无法检测到这一点,因为它无法获取对象的地址或确定 thunk 的状态。在实践中,这种类型的碰撞很少见,原因如下:

  • 并发代码通常以适合特定问题的方式跨线程划分工作负载,因此重叠的风险很低

  • 在达到弱头部正常形式之前,thunk 的评估通常相当“浅”,因此“碰撞”的概率很低

因此,即使在并发上下文中,实现非严格评估时,thunk 最终也会提供良好的性能折衷。

【讨论】:

猜你喜欢
  • 2020-08-23
  • 1970-01-01
  • 2021-01-06
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多