【问题标题】:Haskell evaluation synchronisation between threads线程之间的 Haskell 求值同步
【发布时间】:2020-12-30 15:47:27
【问题描述】:

我试图了解 GHC Haskell 如何在线程之间同步“基本”值(即不是 IORef、TVar 等)的计算。我搜索了这方面的信息,但没有找到任何明确的信息。

以下面的示例程序为例:

import Control.Concurrent

expensiveFunction x = sum [1..x] -- Just an example

val = expensiveFunction 12345

thread1 = print val

thread2 = print val

main = do
    forkOS thread1
    forkOS thread2

我了解值 val 最初将由未评估的闭包表示。为了打印 val,程序必须首先评估它。一旦评估了顶级绑定,就不需要再次评估它。

  1. “val”的表示是否由不同的线程共享?

  2. 如果由于某种原因thread1首先完成了求值,它能否通过交换指针将最终计算值传递给thread2?那将如何同步?

  3. 如果线程 1 忙于评估线程 2 何时需要该值,线程 2 是等待它完成还是他们都争先恐后地评估它?

【问题讨论】:

    标签: haskell ghc


    【解决方案1】:

    在 GHC 编译的程序中,值会经历三个(-ish)评估阶段:

    1. 咚。这是他们开始的地方。
    2. 黑洞。当被强制时,一个 thunk 被转换为一个黑洞并开始计算。其他请求黑洞值的线程会将自己添加到黑洞更新时的通知列表中。 (此外,如果 thunk 本身尝试访问黑洞,它将短路到异常而不是永远等待。)
    3. 已评估。计算完成后,它的最后一项任务是将黑洞更新为普通值(嗯,无论如何,WHNF 值)。

    在这些阶段转换期间更新的指针与其他线程共享,并且受到竞争条件的保护。这意味着,在极少数情况下,两个(或更多)线程可能都在阶段 1 中看到一个指针,并且两者都执行 1 -> 2 转换;在这种情况下,两者都将评估 thunk,并且转换 2 -> 3 也将发生两次。但值得注意的是,1 -> 2 转换通常比它要替换的计算快得多(本质上只是一两次内存访问),部分原因是竞争难以触发。

    因为语言是纯正的,所以赛车的线索会得出相同的答案。所以这里没有语义上的困难。但在极少数情况下,可能会重复一些工作。非常非常罕见的是,每次 1 -> 2 转换时锁定的开销会比这种轻微的重复更好。 (如果您发现这种情况,请考虑手动保护对共享的任何昂贵内容的评估!)

    推论:必须非常小心不安全的IO a -> a 系列函数;一些保证结果a的评估同步,而另一些则不保证。如果你的IO a 动作没有你承诺的那么纯粹,并且一场比赛导致它被执行了两次,那么各种奇怪的 heisenbugs 都可能发生。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-05
      • 1970-01-01
      • 2017-12-08
      • 2014-11-02
      • 2013-08-10
      • 1970-01-01
      • 2014-07-30
      • 1970-01-01
      相关资源
      最近更新 更多