Többszálú végrehajtás

Többszálú végrehajtás

Egymással kommunikáló szálakat csak a számítások szintjén lehet megvalósítani.

Új szál indítása az IO monádban (Control.Concurrent modulból):

forkIO :: IO () -> IO ThreadId

:: IO () → IO ThreadId

Példa:

forkIO $ do {threadDelay 1000000; putStr "üdv"}

Kommunikáció

A szálak közti kommunikációra a standard output nem alkalmas.

A kommunikáció osztott szinkronizált állapoton keresztül lehetséges.

Szinkronizáció MVar alapon

A Control.Concurrent.MVar modulból:

-- data MVar a

Két állapot: vagy üres vagy a típusú érték van benne.

Alapműveletek:

newMVar  ::      a      IO (MVar a)
takeMVar :: MVar a      IO a
putMVar  :: MVar a  a  IO ()

A szál blokkolódik amíg nem sikerül elvégezni a műveleteket.

Kifejezőerő

A többszálú végrehajtás tud legalább annyit, mint a többszálú kiértékelés:

parPlus :: Num a => a -> a -> IO a
x `parPlus` y =  do 
      box <- newEmptyMVar
      forkIO (x `pseq` putMVar box x)
      y `pseq` return ()
      x <- takeMVar box
      return (x + y)

Feladat

Készítsünk egy függvényt, ami két kifejezést párhuzamosan értékel ki, és az előbb befejeződő kiértékelés végeredményével tér vissza!

merge :: a -> a -> IO a
merge x y = do
    mv <- newEmptyMVar
    id1 <- forkIO $ x `pseq` putMVar mv x
    id2 <- forkIO $ y `pseq` putMVar mv y
    v <- takeMVar mv
    killThread id1
    killThread id2
    return v

Teszteset:

-- 3 `merge` fib 20 ~~ return 3

Segítség: Figyeljünk arra, hogy az végeredmény megkapása után ne maradjon futó szál!

killThread :: ThreadId -> IO ()

Szinkronizáció TVar alapon

STM: Software transactional memory

Az stm csomagban levő Control.Concurrent.STM modult használjuk.

Elemi műveletek

Az STM hasonlít az IO-hoz, de a számításai atomiak:

-- data STM a

Atomi számítás végrehajtása:

atomically :: STM a  IO a

Tranzakciós változók

A TVar műveletei hasonlóak az IOVar műveleteihez:

-- data TVar a
newTVar   ::      a      STM (TVar a)
readTVar  :: TVar a      STM a
writeTVar :: TVar a  a  STM ()

Előny: miközben egymás után írjuk és olvassuk a változókat, más biztos hogy nem nyúl hozzájuk.

Példa

transfer :: TVar Int -> TVar Int -> Int -> IO ()
transfer from to amount =
  atomically $ do
    balance <- readTVar from
    if balance < amount
      then retry
      else do
         writeTVar from (balance - amount)
         tobalance <- readTVar to
         writeTVar to (tobalance + amount)

Számítás újrajátszása:

retry :: STM a

orElse

transfer addig próbálkozik, amíg le nem emelhető az összeg (csak változás esetén próbálja újra). Hogy tudunk mást csinálni, hogy ha nem sikerült?

-- transfer `orElse` ...

orElse típusa és néhány törvény:

orElse :: STM a  STM a  STM a
-- a `orElse` (b `orElse` c) === (a `orElse` b) `orElse` c
-- retry `orElse`     m      === m
-- m     `orElse` retry      === m