Подтвердить что ты не робот

Haskell concurrency - forkIO действительно недетерминирован?

Я пытаюсь использовать concurrency в Haskell для конкретной оптимизации, в которой требуется только одно из двух значений, и в зависимости от ситуации либо один может быть намного быстрее, чем другой.

Я думал, что могу просто запустить 2 потока с forkIO, а затем подождать, пока значение не будет размещено в MVar. Вот простой тест, который я написал для этого:

import Control.Concurrent

main = do out <- newEmptyMVar
          t1 <- forkIO (makeString out)
          t2 <- forkIO (makeInt out)
          v <- takeMVar out
          killThread t1
          killThread t2
          case v of
               Left s -> putStrLn s
               Right i -> putStrLn $ show i

makeString out = do s <- return ( show (primes !! 10000))
                    putMVar out $ Left s

makeInt out = do i <- return 2
                 putMVar out $ Right i

primes = sieve [2..] 
 where sieve (x:xs) = x : (sieve $ filter ((/=0).(flip mod x)) xs)

Скомпилировано с помощью

ghc --make -threaded Test

Однако, только левый случай когда-либо достигнут, хотя получение начального числа должно занять достаточно много времени, чтобы начать makeInt (и возврат 2 действительно не займет столько времени). Почему это и как это исправить?

4b9b3361

Ответ 1

Проблема здесь в лень. makeString просто вставляет thunk для вычисления show (primes !! 10000), который затем затем оценивается основным потоком. Вставка кулака довольно быстро, поэтому в этом случае выигрывает гонка.

Чтобы заставить оценку произойти в потоке, вы можете изменить return на evaluate:

makeString out = do s <- evaluate $ show (primes !! 10000)
                    putMVar out $ Left s

Это должно привести к тому, что makeInt выиграет гонку в большинстве случаев (хотя она не гарантируется).

Ответ 2

Да, потоки действительно не детерминированы (в GHC).

Просто ваш конкретный код структурирован и оптимизирован таким образом, чтобы t1 всегда выигрывал. Нет гарантий.

Если вы хотите попробовать массажировать его для получения другого результата, попробуйте включить оптимизацию (-O2) и/или используя несколько ядер (+RTS -N).

например. на моей машине два пробега подряд:

$ ghc -O2 -threaded --make A.hs -rtsopts -fforce-recomp
[1 of 1] Compiling Main             ( A.hs, A.o )
Linking A.exe ...
$ ./A +RTS -N2
2
$ ./A +RTS -N2
104743

Как указывает хаммар, вы можете также структурировать свой код, чтобы заставить больше работать в потоке (или переключиться на