Haskell Language
Concurrence
Recherche…
Remarques
Les bonnes ressources pour apprendre sur la programmation concurrente et parallèle dans Haskell sont:
Fils de frai avec `forkIO`
Haskell prend en charge de nombreuses formes d' forkIO
simultané et la plus évidente forkIO
à forkIO
un thread à l'aide de forkIO
.
La fonction forkIO :: IO () -> IO ThreadId
prend une action IO
et retourne son ThreadId
, tandis que l'action sera exécutée en arrière-plan.
Nous pouvons démontrer cela de manière succincte en utilisant 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
Les deux actions se dérouleront en arrière-plan et la seconde sera presque terminée avant la dernière!
Communication entre les threads avec `MVar`
Il est très facile de transmettre des informations entre les threads en utilisant le type MVar a
et ses fonctions Control.Concurrent
dans Control.Concurrent
:
-
newEmptyMVar :: IO (MVar a)
- crée un nouveauMVar a
-
newMVar :: a -> IO (MVar a)
- crée un nouveauMVar
avec la valeur donnée -
takeMVar :: MVar a -> IO a
- récupère la valeur duMVar
donné, ou bloque jusqu'à ce qu'une soit disponible -
putMVar :: MVar a -> a -> IO ()
- met la valeur donnée dans leMVar
, ou bloque jusqu'à ce qu'il soit vide
Sommez les nombres de 1 à 100 millions dans un thread et attendez le résultat:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
Une démonstration plus complexe pourrait consister à prendre l’entrée utilisateur et la somme en arrière-plan en attendant d’autres entrées:
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
Comme indiqué plus haut, si vous appelez takeMVar
et que le MVar
est vide, il se bloque jusqu'à ce qu'un autre thread insère quelque chose dans le MVar
, ce qui pourrait entraîner un problème takeMVar
aux philosophes de la restauration . La même chose se produit avec putMVar
: si c'est plein, ça va bloquer jusqu'à ce qu'il soit vide!
Prenez la fonction suivante:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
Nous exécutons les deux fonctions avec des MVar
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
Ce qui pourrait arriver, c'est que:
- Le fil 1 lit
ma
et bloquema
- Le thread 2 lit
mb
et bloque doncmb
Maintenant, le thread 1 ne peut pas lire mb
car le thread 2 l'a bloqué et le thread 2 ne peut pas lire ma
car le thread 1 l'a bloqué. Une impasse classique!
Blocs atomiques avec mémoire transactionnelle logicielle
Un autre outil concurrentiel puissant et évolué de Haskell est Software Transactional Memory, qui permet à plusieurs threads d'écrire de manière atomique sur une seule variable de type TVar a
.
TVar a
est le type principal associé à la monade STM
et représente la variable transactionnelle. Ils sont utilisés de la même manière que MVar
mais dans la monade STM
grâce aux fonctions suivantes:
atomically :: STM a -> IO a
Effectuez une série d'actions STM de manière atomique.
readTVar :: TVar a -> STM a
Lisez la TVar
la TVar
, par exemple:
value <- readTVar t
writeTVar :: TVar a -> a -> STM ()
Ecrivez une valeur à la TVar
donnée.
t <- newTVar Nothing writeTVar t (Just "Hello")
Cet exemple est tiré du 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