Haskell Language
samenloop
Zoeken…
Opmerkingen
Goede bronnen voor het leren over gelijktijdig en parallel programmeren in Haskell zijn:
Uitdraaiende draden met `forkIO`
Haskell ondersteunt vele vormen van gelijktijdigheid en het meest voor de hand liggende is om een thread te forken met behulp van forkIO
.
De functie forkIO :: IO () -> IO ThreadId
voert een IO
actie uit en retourneert zijn ThreadId
, terwijl de actie op de achtergrond wordt uitgevoerd.
We kunnen dit vrij bondig demonstreren met behulp van 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
Beide acties worden op de achtergrond uitgevoerd en de tweede wordt bijna gegarandeerd vóór de laatste voltooid!
Communiceren tussen threads met `MVar`
Het is heel gemakkelijk om informatie door te geven tussen threads met behulp van de MVar a
type en de bijbehorende functies in Control.Concurrent
.
-
newEmptyMVar :: IO (MVar a)
- maakt een nieuweMVar a
-
newMVar :: a -> IO (MVar a)
- maakt een nieuweMVar
met de opgegeven waarde -
takeMVar :: MVar a -> IO a
- haalt de waarde op van de gegevenMVar
, of blokkeert totdat er een beschikbaar is -
putMVar :: MVar a -> a -> IO ()
- zet de gegeven waarde in deMVar
, of blokkeert totdat deze leeg is
Laten we de getallen van 1 tot 100 miljoen in een thread samenvatten en wachten op het resultaat:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
Een meer complexe demonstratie zou kunnen zijn om gebruikersinvoer en som op de achtergrond te nemen in afwachting van meer invoer:
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
Zoals eerder gezegd, als je takeMVar
en de MVar
leeg is, blokkeert deze totdat een andere thread iets in de MVar
, wat kan leiden tot een probleem met Dining Philosophers . Hetzelfde gebeurt met putMVar
: als het vol is, blokkeert het totdat het leeg is!
Neem de volgende functie:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
We voeren de twee functies uit met enkele MVar
's
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
Wat zou kunnen gebeuren is dat:
- Thread 1 leest
ma
en blokkeertma
- Thread 2 leest
mb
en blokkeert dusmb
Nu kan draad 1 mb
niet lezen omdat draad 2 het heeft geblokkeerd, en draad 2 kan ma
niet lezen omdat draad 1 het heeft geblokkeerd. Een klassieke impasse!
Atoomblokken met Software Transactional Memory
Een ander krachtig en volwassen hulpmiddel voor gelijktijdigheid in Haskell is Software Transactional Memory, waarmee meerdere threads op atomaire wijze naar een enkele variabele van het type TVar a
kunnen schrijven.
TVar a
is het TVar a
geassocieerd met de STM
monade en staat voor transactionele variabele. Ze worden net als MVar
maar binnen de STM
monade via de volgende functies:
atomically :: STM a -> IO a
Voer een reeks STM-acties atomisch uit.
readTVar :: TVar a -> STM a
Lees de waarde van de TVar
, bijvoorbeeld:
value <- readTVar t
writeTVar :: TVar a -> a -> STM ()
Schrijf een waarde voor de gegeven TVar
.
t <- newTVar Nothing writeTVar t (Just "Hello")
Dit voorbeeld is afkomstig van de Haskell Wiki:
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