Zoeken…


Opmerkingen

Goede bronnen voor het leren over gelijktijdig en parallel programmeren in Haskell zijn:

Uitdraaiende draden met `forkIO`

Haskell ondersteunt vele vormen van gelijktijdigheid en het meest voor de hand liggende is om een thread te forken met behulp van forkIO .

De functie forkIO :: IO () -> IO ThreadId voert een IO actie uit en retourneert zijn ThreadId , terwijl de actie op de achtergrond wordt uitgevoerd.

We kunnen dit vrij bondig demonstreren met behulp van 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

Beide acties worden op de achtergrond uitgevoerd en de tweede wordt bijna gegarandeerd vóór de laatste voltooid!

Communiceren tussen threads met `MVar`

Het is heel gemakkelijk om informatie door te geven tussen threads met behulp van de MVar a type en de bijbehorende functies in Control.Concurrent .

  • newEmptyMVar :: IO (MVar a) - maakt een nieuwe MVar a
  • newMVar :: a -> IO (MVar a) - maakt een nieuwe MVar met de opgegeven waarde
  • takeMVar :: MVar a -> IO a - haalt de waarde op van de gegeven MVar , of blokkeert totdat er een beschikbaar is
  • putMVar :: MVar a -> a -> IO () - zet de gegeven waarde in de MVar , of blokkeert totdat deze leeg is

Laten we de getallen van 1 tot 100 miljoen in een thread samenvatten en wachten op het resultaat:

import Control.Concurrent
main = do
  m <- newEmptyMVar
  forkIO $ putMVar m $ sum [1..10000000]
  print =<< takeMVar m  -- takeMVar will block 'til m is non-empty!

Een meer complexe demonstratie zou kunnen zijn om gebruikersinvoer en som op de achtergrond te nemen in afwachting van meer invoer:

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

Zoals eerder gezegd, als je takeMVar en de MVar leeg is, blokkeert deze totdat een andere thread iets in de MVar , wat kan leiden tot een probleem met Dining Philosophers . Hetzelfde gebeurt met putMVar : als het vol is, blokkeert het totdat het leeg is!

Neem de volgende functie:

concurrent ma mb = do
  a <- takeMVar ma
  b <- takeMVar mb
  putMVar ma a
  putMVar mb b

We voeren de twee functies uit met enkele MVar 's

concurrent ma mb     -- new thread 1 
concurrent mb ma     -- new thread 2

Wat zou kunnen gebeuren is dat:

  1. Thread 1 leest ma en blokkeert ma
  2. Thread 2 leest mb en blokkeert dus mb

Nu kan draad 1 mb niet lezen omdat draad 2 het heeft geblokkeerd, en draad 2 kan ma niet lezen omdat draad 1 het heeft geblokkeerd. Een klassieke impasse!

Atoomblokken met Software Transactional Memory

Een ander krachtig en volwassen hulpmiddel voor gelijktijdigheid in Haskell is Software Transactional Memory, waarmee meerdere threads op atomaire wijze naar een enkele variabele van het type TVar a kunnen schrijven.

TVar a is het TVar a geassocieerd met de STM monade en staat voor transactionele variabele. Ze worden net als MVar maar binnen de STM monade via de volgende functies:

atomically :: STM a -> IO a

Voer een reeks STM-acties atomisch uit.

readTVar :: TVar a -> STM a

Lees de waarde van de TVar , bijvoorbeeld:

value <- readTVar t

writeTVar :: TVar a -> a -> STM ()

Schrijf een waarde voor de gegeven TVar .

t <- newTVar Nothing
writeTVar t (Just "Hello")

Dit voorbeeld is afkomstig van de 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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow