是评估还是 $!足以在多线程 monadic 上下文中强制使用 WHNF 值,还是我需要 pseq?

Is evaluate or $! sufficient to WHNF-force a value in a multithreaded monadic context, or do I need pseq?

以下似乎有效(如:它每秒都在说 Surely tomorrow

import Control.Concurrent
import Control.Concurrent.MVar

import Control.Exception (evaluate)

main :: IO ()
main = do
    godot <- newEmptyMVar
    forkIO $ do
        g <- evaluate $ last [0..]
        putMVar godot g
    let loop = do
        threadDelay $ 10^6
        g <- tryTakeMVar godot
        case g of
            Just g -> return ()
            Nothing -> putStrLn "Surely tomorrow." >> loop
    loop

这使用 evaluate 来确保 last [0..] 在填充 MVar 之前实际上被强制为 WHFN – 如果我将分叉线程更改为

    forkIO $ do
        let g = last [0..]
        putMVar godot g

然后程序终止。

然而,evaluate 使用 seq。在确定性并行的上下文中,总是强调 seq 不足以实际保证评估顺序。这个问题不会出现在 monadic 上下文中,还是我应该更好地使用

    forkIO $ do
        let g = last [0..]
        g `pseq` putMVar godot g

确保编译器无法重新排序求值以便 tryTakeMVar 过早成功?

如果我没有完全错的话,将 last [0..] 计算为 WHNF 将花费无限长的时间,因为 Int 的 WHNF 意味着您知道确切的数字。

putMVarlast [0..] 被评估为 WHNF 之前不会开始执行(我们知道这需要永远),因为 putMVar 将需要 RealWorld-token ( s) return 由对 evaluate 的调用编辑。 (或者更简单地说:evaluate 有效。它仅在评估其对 WHNF 的参数后完成。)

evaluate :: a -> IO a
evaluate a = IO $ \s -> seq# a s
--                     this    ^

putMVar (MVar mvar#) x = IO $ \ s# ->
--           which is used here ^^
    case putMVar# mvar# x s# of
--         is needed here ^^
        s2# -> (# s2#, () #)

其中 seq# 是一个 GHC-prim 函数,它保证 return (# a, s #) 只有在将 a 评估为 WHNF 之后(这就是它的目的)。也就是说,只有在 a 被评估为 WHNF 之后, s 才能在对 putMVar 的调用中使用。虽然这些标记纯属想象("RealWorld is deeply magical..."),但它们受到编译器的尊重,整个 IO-monad 都建立在它之上。

所以是的,在这种情况下 evaluate 就足够了。 evaluateseq 更重要:它结合了 IO-monadic 排序和 seq#-排序来产生它的效果。


事实上,pseq 版本对我来说有点可疑,因为它最终取决于 lazy,其中 evaluate 最终取决于 seq# 和 monadic token -通过。而且我更信任seq#

pseq 的要点是确保在父线程用 par 引发计算后,它不会立即继续尝试评估引发的计算本身的结果,而是先做自己的工作。有关示例,请参见 the documentation。当你更明确地使用并发时,你不应该需要 pseq.