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 nuovo MVar a
  • newMVar :: a -> IO (MVar a) - crea un nuovo MVar con il valore specificato
  • takeMVar :: MVar a -> IO a - recupera il valore dal MVar , o blocca fino a quando uno è disponibile
  • putMVar :: MVar a -> a -> IO () - inserisce il valore dato in MVar , 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:

  1. Il thread 1 legge ma e blocca ma
  2. Thread 2 legge mb e quindi blocca mb

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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow