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 nieuweMVarmet 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
maen blokkeertma - Thread 2 leest
mben 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