Это оценка или $! достаточно, чтобы 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# еще немного.

Другие вопросы по тегам