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 nowy MVar a
  • newMVar :: a -> IO (MVar a) - tworzy nowy MVar o podanej wartości
  • takeMVar :: MVar a -> IO a - pobiera wartość z podanego MVar lub bloków, dopóki nie będzie dostępna
  • putMVar :: MVar a -> a -> IO () - umieszcza podaną wartość w MVar lub 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:

  1. Wątek 1 czyta ma i blokuje ma
  2. Wątek 2 odczytuje mb a tym samym blokuje mb

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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow