Haskell Language
Concorrenza
Ricerca…
Osservazioni
Buone risorse per l'apprendimento della programmazione simultanea e parallela in Haskell sono:
Thread di deposizione con `forkIO`
Haskell supporta molte forme di concorrenza e il più ovvio forkIO
un thread utilizzando forkIO
.
La funzione forkIO :: IO () -> IO ThreadId
accetta un'azione IO
e restituisce il suo ThreadId
, nel frattempo l'azione verrà eseguita in background.
Possiamo dimostrarlo in modo abbastanza sintetico usando 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
Entrambe le azioni verranno eseguite in background, e il secondo è quasi garantito per finire prima dell'ultimo!
Comunicare tra thread con `MVar`
È molto facile passare le informazioni tra i thread utilizzando il MVar a
tipo e le relative funzioni di accompagnamento in Control.Concurrent
:
-
newEmptyMVar :: IO (MVar a)
- crea un nuovoMVar a
-
newMVar :: a -> IO (MVar a)
- crea un nuovoMVar
con il valore specificato -
takeMVar :: MVar a -> IO a
- recupera il valore dalMVar
, o blocca fino a quando uno è disponibile -
putMVar :: MVar a -> a -> IO ()
- inserisce il valore dato inMVar
, o blocchi finché non è vuoto
Sommiamo i numeri da 1 a 100 milioni in una discussione e aspettiamo il risultato:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
Una dimostrazione più complessa potrebbe essere quella di prendere l'input dell'utente e sommare in background in attesa di ulteriori 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
Come detto in precedenza, se chiami takeMVar
e MVar
è vuoto, blocca fino a quando un altro thread mette qualcosa nel MVar
, il che potrebbe causare un problema di Philosophers . La stessa cosa accade con putMVar
: se è pieno, bloccherà finché non sarà vuoto!
Assumi la seguente funzione:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
MVar
le due funzioni con alcuni MVar
s
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
Quello che potrebbe accadere è che:
- Il thread 1 legge
ma
e bloccama
- Thread 2 legge
mb
e quindi bloccamb
Ora Thread 1 non può leggere mb
poiché Thread 2 lo ha bloccato e Thread 2 non può leggere ma
come Thread 1 lo ha bloccato. Un classico punto morto!
Blocchi atomici con memoria transazionale del software
Un altro strumento di concorrenza potente e maturo in Haskell è la memoria transazionale del software, che consente a più thread di scrivere su una singola variabile di tipo TVar a
in modo atomico.
TVar a
è il tipo principale associato alla monade STM
e sta per variabile transazionale. Sono utilizzati molto come MVar
ma all'interno della monade STM
attraverso le seguenti funzioni:
atomically :: STM a -> IO a
Eseguire una serie di azioni STM atomicamente.
readTVar :: TVar a -> STM a
Leggi il TVar
del TVar
, ad esempio:
value <- readTVar t
writeTVar :: TVar a -> a -> STM ()
Scrivi un valore per il TVar
.
t <- newTVar Nothing writeTVar t (Just "Hello")
Questo esempio è tratto dal 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