Souběžný Haskell - Concurrent Haskell - Wikipedia
Souběžný Haskell rozšiřuje[1] Haskell 98 s explicitní konkurence. Jeho dva hlavní základní koncepty jsou:
- Primitivní typ
MVar α
implementace ohraničeného / jediného místa asynchronní kanál, který je buď prázdný, nebo obsahuje hodnotu typuα
. - Schopnost plodit souběžně vlákno přes
vidlice
primitivní.
Postavený na vrcholu je kolekce užitečných abstrakcí souběžnosti a synchronizace[2] jako neomezené kanály, semafory a ukázkové proměnné.
Vlákna Haskell mají velmi nízkou režii: vytváření, přepínání kontextu a plánování jsou všechny interní pro běh Haskell. Tato vlákna na úrovni Haskell jsou mapována na konfigurovatelný počet vláken na úrovni OS, obvykle jeden na jádro procesoru.
Softwarová transakční paměť
The softwarová transakční paměť (STM) rozšíření[3] na GHC znovu používá proces rozvětvení primitiv Concurrent Haskell. STM však:
- vyhýbá se
MVar
je proTVar
s. - zavádí
opakovat
anebo jinak
primitivum, umožňující alternativu atomové akce být složen spolu.
STM monad
STM monad[4] je implementace softwarové transakční paměti v Haskellu. Je implementován v GHC a umožňuje měnit proměnlivé proměnné transakce.
Tradiční přístup
Vezměme si bankovní aplikaci jako příklad a transakci v ní - funkci převodu, která bere peníze z jednoho účtu a vkládá je na jiný účet. V IO monadě to může vypadat takto:
typ Účet = IORef Celé číslopřevod :: Celé číslo -> Účet -> Účet -> IO ()převod množství z na = dělat od Val <- readIORef z - (A) toVal <- readIORef na writeIORef z (od Val - množství) writeIORef na (toVal + množství)
To způsobí problémy v souběžných situacích, kdy může na serveru probíhat více přenosů stejný účet u stejný čas. Pokud by došlo k dvěma převodům převádějícím peníze z účtu z
a oba hovory k přepojení vedly na linku (A)
dříve, než kterýkoli z nich zapsal své nové hodnoty, je možné, že peníze budou vloženy na další dva účty, přičemž z účtu bude odstraněna pouze jedna z převáděných částek z
, čímž se vytvoří a stav závodu. Bankovní aplikace by tak zůstala v nekonzistentním stavu.
Tradičním řešením takového problému je zamykání. Například zámky lze umístit kolem úprav účtu, aby se zajistilo, že kredity a debety budou probíhat atomicky. V Haskellu se zamykání provádí pomocí MVars:
typ Účet = MVar Celé číslokredit :: Celé číslo -> Účet -> IO ()kredit množství účet = dělat proud <- takeMVar účet putMVar účet (proud + množství)debet :: Celé číslo -> Účet -> IO ()debet množství účet = dělat proud <- takeMVar účet putMVar účet (proud - množství)
Použití těchto postupů zajistí, že peníze nikdy nebudou ztraceny nebo získány v důsledku nesprávného prokládání čtení a zápisů na jakýkoli jednotlivý účet. Pokud se je však pokusíte sestavit dohromady a vytvořit postup, jako je přenos:
převod :: Celé číslo -> Účet -> Účet -> IO ()převod množství z na = dělat debet množství z kredit množství na
podmínka závodu stále existuje: první účet může být odepsán, potom může být pozastaveno provádění vlákna, takže účty jako celek zůstanou v nekonzistentním stavu. Proto musí být přidány další zámky, aby byla zajištěna správnost složených operací, a v nejhorším případě může být potřeba jednoduše uzamknout všechny účty bez ohledu na to, kolik se jich v dané operaci používá.
Atomové transakce
Aby se tomu zabránilo, lze použít STM monad, který umožňuje psát atomické transakce. To znamená, že všechny operace uvnitř transakce jsou zcela dokončeny, aniž by jakákoli jiná vlákna upravovala proměnné, které naše transakce používá, nebo selhala, a stav byl vrácen zpět na místo, kde byl před zahájením transakce. Stručně řečeno, atomové transakce se buď dokončují úplně, nebo je to, jako by se vůbec nespustily. Kód založený na zámku se překládá relativně přímočaře:
typ Účet = TVar Celé číslokredit :: Celé číslo -> Účet -> STM ()kredit množství účet = dělat proud <- readTVar účet writeTVar účet (proud + množství)debet :: Celé číslo -> Účet -> STM ()debet množství účet = dělat proud <- readTVar účet writeTVar účet (proud - množství)převod :: Celé číslo -> Účet -> Účet -> STM ()převod množství z na = dělat debet množství z kredit množství na
Návratové typy STM ()
lze označit, že vytváříme skripty pro transakce. Až přijde čas skutečně provést takovou transakci, funkce atomicky :: STM a -> IO a
se používá. Výše uvedená implementace zajistí, aby žádné jiné transakce nezasahovaly do proměnných, které používá (od a do) během provádění, což vývojáři umožňuje, aby se ujistil, že nedojde k takovým podmínkám jako výše. Lze provést další vylepšení, aby se zajistilo, že některé další “obchodní logika „je v systému udržováno, tj. že transakce by se neměla pokoušet přijímat peníze z účtu, dokud na něm není dostatek peněz:
převod :: Celé číslo -> Účet -> Účet -> STM ()převod množství z na = dělat od Val <- readTVar z -li (od Val - množství) >= 0 pak dělat debet množství z kredit množství na jiný opakovat
Tady opakovat
byla použita funkce, která vrátí transakci zpět a zkuste to znovu. Opakování v STM je chytré v tom, že se nebude pokoušet transakci spustit znovu, dokud nebude některá z proměnných, na které odkazuje během transakce, upravena jiným transakčním kódem. Díky tomu je STM monad docela efektivní.
Ukázkový program využívající funkci přenosu může vypadat takto:
modul Hlavní kdeimport Control.Concurrent (vidlice)import Control.Concurrent.STMimport Ovládání. Monad (navždy)import System.Exit (exitSuccess)typ Účet = TVar Celé číslohlavní = dělat bob <- nový účet 10000 Jill <- nový účet 4000 repeatIO 2000 $ vidlice $ atomicky $ převod 1 bob Jill navždy $ dělat bobBalance <- atomicky $ readTVar bob jillBalance <- atomicky $ readTVar Jill putStrLn („Bobova rovnováha:“ ++ ukázat bobBalance ++ ", Jillina rovnováha:" ++ ukázat jillBalance) -li bobBalance == 8000 pak exitSuccess jiný putStrLn „Zkusím to znovu.“repeatIO :: Celé číslo -> IO A -> IO ArepeatIO 1 m = mrepeatIO n m = m >> repeatIO (n - 1) mnový účet :: Celé číslo -> IO Účetnový účet množství = newTVarIO množstvípřevod :: Celé číslo -> Účet -> Účet -> STM ()převod množství z na = dělat od Val <- readTVar z -li (od Val - množství) >= 0 pak dělat debet množství z kredit množství na jiný opakovatkredit :: Celé číslo -> Účet -> STM ()kredit množství účet = dělat proud <- readTVar účet writeTVar účet (proud + množství)debet :: Celé číslo -> Účet -> STM ()debet množství účet = dělat proud <- readTVar účet writeTVar účet (proud - množství)
který by měl vytisknout „Bobův zůstatek: 8000, Jillin zůstatek: 6000“. Tady atomicky
funkce byla použita ke spuštění akcí STM v IO monadě.
Reference
- ^ Simon Peyton Jones, Andrew D. Gordon a Sigbjorn Finne. Souběžný Haskell. Sympozium ACM SIGPLAN-SIGACT o zásadách programovacích jazyků (PoPL). 1996. (Některé oddíly jsou s ohledem na aktuální implementaci zastaralé.)
- ^ The Haskell Hierarchické knihovny, Control.Concurrent Archivováno 2012-08-02 v Archiv. Dnes
- ^ Tim Harris, Simon Marlow, Simon Peyton Jones a Maurice Herlihy. Transakce skladatelné paměti. ACM Sympózium o zásadách a praxi paralelního programování 2005 (PPoPP'05). 2005.
- ^ Control.Concurrent.STM