是评估还是 $!足以在多线程 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 意味着您知道确切的数字。
putMVar
在 last [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
就足够了。 evaluate
比 seq
更重要:它结合了 IO-monadic 排序和 seq#
-排序来产生它的效果。
事实上,pseq
版本对我来说有点可疑,因为它最终取决于 lazy
,其中 evaluate
最终取决于 seq#
和 monadic token -通过。而且我更信任seq#
。
pseq
的要点是确保在父线程用 par
引发计算后,它不会立即继续尝试评估引发的计算本身的结果,而是先做自己的工作。有关示例,请参见 the documentation。当你更明确地使用并发时,你不应该需要 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 意味着您知道确切的数字。
putMVar
在 last [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
就足够了。 evaluate
比 seq
更重要:它结合了 IO-monadic 排序和 seq#
-排序来产生它的效果。
事实上,pseq
版本对我来说有点可疑,因为它最终取决于 lazy
,其中 evaluate
最终取决于 seq#
和 monadic token -通过。而且我更信任seq#
。
pseq
的要点是确保在父线程用 par
引发计算后,它不会立即继续尝试评估引发的计算本身的结果,而是先做自己的工作。有关示例,请参见 the documentation。当你更明确地使用并发时,你不应该需要 pseq
.