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 nouveau MVar a
  • newMVar :: a -> IO (MVar a) - crée un nouveau MVar avec la valeur donnée
  • takeMVar :: MVar a -> IO a - récupère la valeur du MVar donné, ou bloque jusqu'à ce qu'une soit disponible
  • putMVar :: MVar a -> a -> IO () - met la valeur donnée dans le MVar , 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:

  1. Le fil 1 lit ma et bloque ma
  2. Le thread 2 lit mb et bloque donc mb

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


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow