Это оценка или $! достаточно, чтобы WHNF-форсировал значение в многопоточном монадном контексте, или мне нужен 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..]
на самом деле вынужден WHFN перед заполнением MVar
- если я поменяю разветвленную нить на
forkIO $ do
let g = last [0..]
putMVar godot g
затем программа завершается.
Тем не мение, evaluate
использования seq
, В контексте детерминистического параллелизма всегда подчеркивается, что seq
недостаточно, чтобы фактически гарантировать порядок оценки. Эта проблема не возникает в монадическом контексте, или я должен лучше использовать
forkIO $ do
let g = last [0..]
g `pseq` putMVar godot g
чтобы компилятор не мог переупорядочить оценку так tryTakeMVar
преждевременно?
2 ответа
Точка pseq
чтобы убедиться, что после того, как родительский поток запустит вычисление с par
, он не сразу начинает пытаться оценить результат самих вычислений, но вместо этого выполняет свою собственную работу. Смотрите документацию для примера. Когда вы работаете более четко с параллелизмом, вам не нужно pseq
,
Если я не совсем ошибаюсь, оценивая last [0..]
WHNF займет бесконечное количество времени, потому что WHNF для Int
означает, что вы знаете точное число.
putMVar
не начнет выполняться раньше last [0..]
оценивается в WHNF (что, как мы знаем, занимает вечность), потому что putMVar
понадобится RealWorld
знак (s
) возвращается по вызову 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, которая гарантирует возврат (# a, s #)
только после оценки a
для WHNF (это его цель). То есть только после a
оценивается в WHNF, s
может быть использован в вызове putMVar
, Хотя эти токены являются чисто образными ("RealWorld глубоко волшебен..."), они соблюдаются компилятором, и вся IO-монада построена поверх него.
Так да, evaluate
достаточно в этом случае. evaluate
больше чем seq
: он сочетает в себе IO-монадическое секвенирование с seq#
последовательность для получения эффекта.
На самом деле, pseq
версия выглядит немного подозрительно для меня, потому что это в конечном итоге зависит от lazy
, где evaluate
в конечном итоге зависит от seq#
и монадическое прохождение токена. И я верю seq#
еще немного.