Haskell Language
Parallelität
Suche…
Bemerkungen
Gute Ressourcen für das Erlernen der gleichzeitigen und parallelen Programmierung in Haskell sind
Laichgewinde mit `forkIO`
Haskell unterstützt viele Formen der Parallelität. Die offensichtlichste forkIO
einen Thread mithilfe von forkIO
.
Die Funktion forkIO :: IO () -> IO ThreadId
führt eine IO
Aktion aus und gibt ihre ThreadId
, ThreadId
die Aktion im Hintergrund ausgeführt wird.
Mit ghci
können wir das kurz und knapp 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 Aktionen werden im Hintergrund ausgeführt, und die zweite wird fast garantiert vor der letzten beendet!
Kommunikation zwischen Threads mit "MVar"
Es ist sehr einfach, Informationen zwischen Threads mit dem Typ MVar a
und den zugehörigen Funktionen in Control.Concurrent
:
-
newEmptyMVar :: IO (MVar a)
- erstellt eine neueMVar a
-
newMVar :: a -> IO (MVar a)
- erstellt eine neueMVar
mit dem angegebenen Wert -
takeMVar :: MVar a -> IO a
- ruft den Wert von der angegebenenMVar
oder blockiert, bis einer verfügbar ist -
putMVar :: MVar a -> a -> IO ()
- setzt den angegebenen Wert in dieMVar
oder blockiert, bis er leer ist
Summieren Sie die Zahlen von 1 bis 100 Millionen in einem Thread und warten Sie auf das Ergebnis:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
Eine komplexere Demonstration könnte darin bestehen, die Benutzereingaben und die Summe im Hintergrund aufzunehmen, während auf weitere Eingaben gewartet wird:
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
Wenn Sie takeMVar
und die MVar
leer ist, wird sie blockiert, bis ein anderer Thread etwas in die MVar
, was zu einem Problem mit Speisesachen führen kann . Dasselbe passiert mit putMVar
: Wenn es voll ist, wird es blockiert, bis es leer ist!
Nehmen Sie die folgende Funktion vor:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
Wir führen die beiden Funktionen mit einigen MVar
s aus
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
Was passieren könnte ist:
- Thread 1 liest
ma
und blockiertma
- Thread 2 liest
mb
und blockiert somitmb
Fädeln Sie jetzt 1 kann nicht gelesen mb
als Thread 2 es blockiert wurde, und 2 Thread kann nicht lesen ma
als Thread 1 es blockiert hat. Ein klassischer Deadlock!
Atomblöcke mit Software-Transaktionsspeicher
Ein weiteres leistungsfähiges und ausgereiftes Parallelitätswerkzeug in Haskell ist Software Transactional Memory, mit dem mehrere Threads TVar a
in eine einzige Variable vom Typ TVar a
schreiben können.
TVar a
ist der Haupttyp der STM
Monade und steht für Transaktionsvariable. Sie werden ähnlich wie MVar
aber innerhalb der STM
Monade durch die folgenden Funktionen verwendet:
atomically :: STM a -> IO a
Führen Sie eine Reihe von STM-Aktionen atomar aus.
readTVar :: TVar a -> STM a
Lesen Sie den TVar
-Wert, zB:
value <- readTVar t
writeTVar :: TVar a -> a -> STM ()
Schreibe einen Wert in den angegebenen TVar
.
t <- newTVar Nothing writeTVar t (Just "Hello")
Dieses Beispiel stammt aus dem 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