我试图了解 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
Run Code Online (Sandbox Code Playgroud)
我知道值 val 最初将由未评估的闭包表示。为了打印 val,程序必须首先评估它。一旦评估了顶级绑定,就不需要再次评估它。
“val”的表示是否甚至由单独的线程共享?
如果由于某种原因,线程 1 首先完成评估,是否可以通过换出指针将最终计算出的值传送给线程 2?这将如何同步?
如果线程 1 正忙于评估线程 2 想要该值时,线程 2 是等待它完成还是它们都争先恐后地评估它?
在 GHC 编译的程序中,值经历三个(-ish)评估阶段:
即在这些相变得到更新的指针与其他线程共享,不从竞争条件的保护。这意味着,在极少数情况下,两个(或多个)线程可能同时看到阶段 1 中的指针,并且都执行 1 -> 2 转换;在这种情况下,两者都会评估 thunk,并且转换 2 -> 3 也会发生两次。但是,值得注意的是,1 -> 2 转换通常比它正在替换的计算快得多(基本上只是一两次内存访问),部分原因正是如此,竞争很难触发。
因为语言是纯粹的,赛车线程会得出相同的答案。所以这里没有语义上的困难。但在极少数情况下,可能会重复一些工作。非常非常罕见的是,每个 1 -> 2 转换的锁定开销会比这种轻微的重复要好。(如果你发现它是你的情况,考虑手动保护正在共享的任何昂贵的东西的评估!)
推论:必须非常小心地处理不安全IO a -> a
的函数系列;有些保证对结果的评估同步,a
有些则不保证。如果你的IO a
行为不像你承诺的那么纯粹,并且一场比赛导致它被执行两次,那么各种奇怪的黑森虫都会发生。