Haskell Language
Konkurencja
Szukaj…
Uwagi
Dobrym źródłem wiedzy na temat programowania współbieżnego i równoległego w Haskell są:
Spawning Threads with `forkIO`
Haskell obsługuje wiele form współbieżności, a najbardziej oczywistym jest rozwidlanie wątku za pomocą forkIO .
Funkcja forkIO :: IO () -> IO ThreadId wykonuje akcję IO i zwraca jej ThreadId , tymczasem akcja zostanie uruchomiona w tle.
Możemy to zwięźle przedstawić za pomocą ghci :
Prelude Control.Concurrent> forkIO $ (print . sum) [1..100000000]
ThreadId 290
Prelude Control.Concurrent> forkIO $ print "hi!"
"hi!"
-- some time later....
Prelude Control.Concurrent> 50000005000000
Obie akcje będą działać w tle, a druga prawie na pewno zakończy się przed ostatnią!
Komunikacja między wątkami za pomocą `MVar`
Bardzo łatwo jest przekazywać informacje między wątkami za pomocą MVar a type i towarzyszących mu funkcji w Control.Concurrent :
-
newEmptyMVar :: IO (MVar a)- tworzy nowyMVar a -
newMVar :: a -> IO (MVar a)- tworzy nowyMVaro podanej wartości -
takeMVar :: MVar a -> IO a- pobiera wartość z podanegoMVarlub bloków, dopóki nie będzie dostępna -
putMVar :: MVar a -> a -> IO ()- umieszcza podaną wartość wMVarlub blokach, aż będzie pusta
Zsumujmy liczby od 1 do 100 milionów w wątku i poczekaj na wynik:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
Bardziej złożoną demonstracją może być pobranie danych przez użytkownika i podsumowanie w tle podczas oczekiwania na więcej danych:
main2 = loop
where
loop = do
m <- newEmptyMVar
n <- getLine
putStrLn "Calculating. Please wait"
-- In another thread, parse the user input and sum
forkIO $ putMVar m $ sum [1..(read n :: Int)]
-- In another thread, wait 'til the sum's complete then print it
forkIO $ print =<< takeMVar m
loop
Jak wspomniano wcześniej, jeśli wywołasz takeMVar a MVar będzie pusty, blokuje się, dopóki inny wątek nie umieści czegoś w MVar , co może spowodować problem z filozofią MVar . To samo dzieje się z putMVar : jeśli jest pełne, będzie blokować, aż będzie puste!
Weź następującą funkcję:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
MVar dwie funkcje z niektórymi MVar
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
Co może się zdarzyć, to:
- Wątek 1 czyta
mai blokujema - Wątek 2 odczytuje
mba tym samym blokujemb
Teraz wątek 1 nie może odczytać mb ponieważ wątek 2 go zablokował, a wątek 2 nie może odczytać ma ponieważ wątek 1 go zablokował. Klasyczny impas!
Bloki atomowe z programową pamięcią transakcyjną
Innym potężnym i dojrzałym narzędziem do współbieżności w Haskell jest Software Transactional Memory, która pozwala na zapis wielu wątków do jednej zmiennej typu TVar a w sposób atomowy.
TVar a jest głównym typem związanym z TVar a STM i oznacza zmienną transakcyjną. Są one używane podobnie jak MVar ale w MVar STM poprzez następujące funkcje:
atomically :: STM a -> IO a
Wykonaj serię akcji STM atomowo.
readTVar :: TVar a -> STM a
Przeczytaj wartość TVar , np .:
value <- readTVar t
writeTVar :: TVar a -> a -> STM ()
Napisz wartość do podanego TVar .
t <- newTVar Nothing writeTVar t (Just "Hello")
Ten przykład pochodzi z Wiki Haskell:
import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM
main = do
-- Initialise a new TVar
shared <- atomically $ newTVar 0
-- Read the value
before <- atomRead shared
putStrLn $ "Before: " ++ show before
forkIO $ 25 `timesDo` (dispVar shared >> milliSleep 20)
forkIO $ 10 `timesDo` (appV ((+) 2) shared >> milliSleep 50)
forkIO $ 20 `timesDo` (appV pred shared >> milliSleep 25)
milliSleep 800
after <- atomRead shared
putStrLn $ "After: " ++ show after
where timesDo = replicateM_
milliSleep = threadDelay . (*) 1000
atomRead = atomically . readTVar
dispVar x = atomRead x >>= print
appV fn x = atomically $ readTVar x >>= writeTVar x . fn