Haskell Language
samtidighet
Sök…
Anmärkningar
Bra resurser för att lära sig om samtidig och parallell programmering i Haskell är:
Lektrådar med `forkIO`
Haskell stöder många former av samtidighet och det mest uppenbara är att smida en tråd med forkIO
.
Funktionen forkIO :: IO () -> IO ThreadId
gör en IO
åtgärd och returnerar sin ThreadId
, under tiden kommer åtgärden att köras i bakgrunden.
Vi kan demonstrera detta ganska kortfattat med 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
Båda åtgärderna kommer att köras i bakgrunden, och den andra är nästan garanterad att vara klar före den sista!
Kommunikera mellan trådarna med `MVar`
Det är väldigt lätt att skicka information mellan trådar med MVar a
typ och dess tillhörande funktioner i Control.Concurrent
:
-
newEmptyMVar :: IO (MVar a)
- skapar en nyMVar a
-
newMVar :: a -> IO (MVar a)
- skapar en nyMVar
med det angivna värdet -
takeMVar :: MVar a -> IO a
- hämtar värdet från den givnaMVar
, eller blockerar tills ett är tillgängligt -
putMVar :: MVar a -> a -> IO ()
- sätter det givna värdet iMVar
, eller blockerar tills det är tomt
Låt oss summera siffrorna från 1 till 100 miljoner i en tråd och vänta på resultatet:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
En mer komplex demonstration kan vara att ta användarinmatning och summa i bakgrunden medan du väntar på mer input:
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
Som tidigare nämnts, om du kallar takeMVar
och MVar
är tom blockeras den tills en annan tråd sätter något i MVar
, vilket kan leda till ett Dining Philosophers Problem . Samma sak händer med putMVar
: om det är fullt blockeras det tills det är tomt!
Ta följande funktion:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
Vi kör de två funktionerna med några MVar
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
Det som kan hända är att:
- Tråd 1 läser
ma
och blockerarma
- Tråd 2 läser
mb
och blockerar därmedmb
Nu kan tråd 1 inte läsa mb
eftersom tråd 2 har blockerat den, och tråd 2 kan inte läsa ma
eftersom tråd 1 har blockerat den. Ett klassiskt dödläge!
Atomblock med mjukvara Transaktionsminne
Ett annat kraftfullt och moget samtidighetsverktyg i Haskell är Software Transactionional Memory, som gör det möjligt för flera trådar att skriva till en enda variabel av typen TVar a
på ett atomiskt sätt.
TVar a
är den huvudtyp som är associerad med STM
monaden och står för transaktionsvariabel. De används ungefär som MVar
men inom STM
monaden genom följande funktioner:
atomically :: STM a -> IO a
Utför en serie STM-åtgärder atomiskt.
readTVar :: TVar a -> STM a
Läs TVar
värde, t.ex.:
value <- readTVar t
writeTVar :: TVar a -> a -> STM ()
Skriv ett värde till den givna TVar
.
t <- newTVar Nothing writeTVar t (Just "Hello")
Detta exempel är hämtat från 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