2012-04-20 17 views
13

Próbuję użyć współbieżności w Haskell dla konkretnej optymalizacji, w której wymagana jest tylko jedna z dwóch wartości, a w zależności od sytuacji jedna z nich może być znacznie szybsza do stworzenia niż druga.Współbieżność Haskella - czy forkIO jest naprawdę niedeterministyczne?

Pomyślałem, że mogę uruchomić 2 wątki z forkIO, a następnie poczekać, aż wartość zostanie umieszczona w MVar. Oto prosty test Pisałem o tym:

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) 

skompilowany z:

ghc --make -threaded Test 

jednak jedyny przypadek lewicy s kiedykolwiek osiągnięty, chociaż coraz prime powinien wystarczająco długo dla makeInt wątek, aby rozpocząć (i powrót 2 naprawdę nie powinno zająć tyle czasu). Dlaczego tak jest i jak to naprawić?

+0

Jak uruchomić swój kod? Domyślnie haskell używa lekkich wątków zamiast rzeczywistych wątków systemu operacyjnego. Nie znam szczegółów, ale to może zmienić politykę planowania lotów. –

+0

Może to nie pasuje do twojej sprawy, ale możesz również przyjrzeć się, jak wygląda praca nad "spekulatywnym paralelizmem": http://hackage.haskell.org/package/speculation – jberryman

+3

FYI, http://hackage.haskell.org/package/monad-par udostępnia dość przyjemny, zewnętrznie czysty interfejs równoległy API, który jednak pozwala wyraźnie określić rozwidlenia, łączenia itp. –

Odpowiedz

20

Problemem jest tu lenistwo. makeString jest po prostu wstawieniem thunk do obliczenia show (primes !! 10000), który następnie zostanie oceniony przez główny wątek później. Wstawianie thunk jest dość szybkie, więc wygrywa wyścig w tym przypadku.

Aby wymusić ocenę wydarzy w wątku, można zmienić return do evaluate:

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

Powinno to spowodować makeInt wygrać wyścig w większości przypadków (choć nie jest to gwarantowane).

+1

Dzięki! Całkowicie zapomniałem wyjaśnić lenistwo. – Cubic

10

Tak, nici naprawdę nie są deterministyczne (w GHC).

Po prostu twój kod jest skonstruowany i zoptymalizowany w taki sposób, że zawsze wygrywa t1. Nie ma żadnych gwarancji.

Jeśli chcesz spróbować masować, aby uzyskać inny wynik, spróbuj włączyć optymalizacje (-O2) i/lub używając wielu rdzeni (+RTS -N).

E.g. na moim komputerze, dwa przebiegi rzędu:

$ 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 

Jak hammar zwraca uwagę, można również zorganizować swój kod, aby wymusić więcej pracy odbędzie się w wątku (lub przełączyć się using strict mvars).

+0

Dziękuję - nie wiedziałem nawet, że mogę przekazać parametry do RTS. – Cubic

Powiązane problemy