समवर्ती हास्केल
समवर्ती (कॉन्कररेंट) हास्केल विस्तारित[1] हास्केल 98 स्पष्ट समरूपता के साथ, इसकी दो मुख्य अंतर्निहित अवधारणाएँ हैं:
- एक आदिम प्रकार
MVar α
एक बाउंडेड/सिंगल-प्लेस एक्टर मॉडल और प्रोसेस कैलकुली # एसिंक्रोनस चैनल को लागू करना, जो या तो खाली है या टाइप का मान रखता हैα
. - के माध्यम से एक समवर्ती थ्रेड (कंप्यूटिंग) उत्पन्न करने की क्षमता
forkIO
प्राचीन हैं।
इसके ऊपर निर्मित उपयोगी संगामिति और तुल्यकालन सार का एक संग्रह है[2] जैसे कि एक्टर मॉडल (कंप्यूटर विज्ञानं) और प्रक्रिया गणना अतुल्यकालिक चैनल, सेमाफोर (प्रोग्रामिंग) और नमूना चर।
हास्केल थ्रेड्स में बहुत कम ओवरहेड होता है: हास्केल रनटाइम के लिए निर्माण, संदर्भ-स्विचिंग और शेड्यूलिंग सभी आंतरिक हैं। ये हास्केल-स्तरीय धागे ओएस-स्तरीय धागे की कॉन्फ़िगर करने योग्य संख्या पर मैप किए जाते हैं, सामान्यतः प्रति प्रोसेसर कोर में से एक होते हैं।
सॉफ्टवेयर ट्रांजैक्शनल मेमोरी
सॉफ्टवेयर ट्रांजैक्शनल मेमोरी (एसटीएम) एक्सटेंशन[3] ग्लासगो के ग्लासगो हास्केल कंपाइलर समवर्ती हास्केल के फोर्किंग प्रिमिटिव की प्रक्रिया का पुन: उपयोग करता है। एसटीएम हालांकि:
- टालता है
MVar
एस के पक्ष मेंTVar
एस। - परिचय देता है
retry
औरorElse
प्रिमिटिव, वैकल्पिक परमाणु क्रियाओं को सॉफ्टवेयर ट्रांजैक्शनल मेमोरी # कंपोज़ेबल ऑपरेशंस एक साथ करने की अनुमति देता है।
एसटीएम मोनाड
एसटीएम मोनाड (कार्यात्मक प्रोग्रामिंग)[4] हास्केल में सॉफ्टवेयर ट्रांजैक्शनल मेमोरी का कार्यान्वयन है। यह जीएचसी में लागू किया गया है, और डाटाबेस लेनदेन में परिवर्तनीय चर को संशोधित करने की अनुमति देता है।
पारंपरिक दृष्टिकोण
एक उदाहरण के रूप में बैंकिंग एप्लिकेशन और उसमें एक ट्रांसक्शन्स पर विचार करें - स्थानांतरण फ़ंक्शन, जो एक खाते से पैसा लेता है, और इसे दूसरे खाते में डालता है। IO मोनाड में, यह ऐसा दिखाई दे सकता है:
type Account = IORef Integer
transfer :: Integer -> Account -> Account -> IO ()
transfer amount from to = do
fromVal <- readIORef from -- (A)
toVal <- readIORef to
writeIORef from (fromVal - amount)
writeIORef to (toVal + amount)
यह समवर्ती स्थितियों में समस्याएँ पैदा करता है जहाँ एक ही समय में एक ही खाते में कई स्थानान्तरण हो सकते हैं। यदि खाते से पैसे ट्रांसफर करने वाले दो स्थानान्तरण होते हैं from
, और दोनों कॉल को स्थानांतरित करने के लिए लाइन चला गया (A)
इससे पहले कि उनमें से कोई भी अपने नए मूल्यों को लिखे, यह संभव है कि पैसा अन्य दो खातों में डाल दिया जाएगा, जिसमें से केवल एक राशि को खाते से हटा दिया जाएगा। from
, इस प्रकार रेस कंडीशन (उच्छृंखल अवस्था) उतपन्न कर रहा है। यह बैंकिंग एप्लिकेशन को असंगत स्थिति में छोड़ देगा।
ऐसी समस्या का पारंपरिक समाधान लॉकिंग है। उदाहरण के लिए, यह सुनिश्चित करने के लिए कि क्रेडिट और डेबिट संपूर्ण या बिलकुल (ऑटोमिकाली) नहीं होते हैं, किसी खाते में संशोधनों के आसपास लॉक लगाए जा सकते हैं। हास्केल में, लॉकिंग MVars के साथ पूरा किया जाता है:
type Account = MVar Integer
credit :: Integer -> Account -> IO ()
credit amount account = do
current <- takeMVar account
putMVar account (current + amount)
debit :: Integer -> Account -> IO ()
debit amount account = do
current <- takeMVar account
putMVar account (current - amount)
इस तरह की प्रक्रियाओं का उपयोग करने से यह सुनिश्चित होगा कि किसी भी व्यक्तिगत खाते में पढ़ने और लिखने के अनुचित इंटरलीविंग के कारण पैसा कभी भी खोया या प्राप्त नहीं होगा। हालाँकि, यदि कोई स्थानांतरण जैसी प्रक्रिया बनाने के लिए उन्हें एक साथ बनाने की कोशिश करता है:
transfer :: Integer -> Account -> Account -> IO ()
transfer amount from to = do
debit amount from
credit amount to
एक रेस कंडीशन की स्थिति अभी भी उपस्थित है: पहले खाते को डेबिट किया जा सकता है, फिर थ्रेड का निष्पादन निलंबित किया जा सकता है, खातों को एक असंगत स्थिति में छोड़ दिया जा सकता है। इस प्रकार, समग्र संचालन की शुद्धता सुनिश्चित करने के लिए अतिरिक्त ताले जोड़े जाने चाहिए, और सबसे खराब स्थिति में, किसी दिए गए ऑपरेशन में कितने भी उपयोग किए जाते हैं, इस पर ध्यान दिए बिना किसी को भी सभी खातों को लॉक करने की आवश्यकता हो सकती है।
एटोमिक्स ट्रांसक्शन्स
इससे बचने के लिए, कोई एसटीएम मोनैड का उपयोग कर सकता है, जो किसी को एटोमिक्स ट्रांसक्शन्स (लेनदेन) लिखने की अनुमति देता है। इसका मतलब यह है कि ट्रांसक्शन्स के अंदर सभी ऑपरेशन पूरी तरह से पूर्ण हो जाते हैं, बिना किसी अन्य थ्रेड के उन चरों को संशोधित करते हैं जो हमारे ट्रांसक्शन्स का उपयोग कर रहे हैं, या यह विफल हो जाता है, और ट्रांसक्शन्स प्रारम्भ होने से पहले स्थिति को वापस ले लिया जाता है। संक्षेप में, परमाणु ट्रांसक्शन्स या तो पूरी तरह से पूरा हो गया है, या ऐसा लगता है जैसे वे कभी भी चलाए ही नहीं गए थे।उपरोक्त लॉक-आधारित कोड अपेक्षाकृत सरल तरीके से अनुवाद करता है:
type Account = TVar Integer
credit :: Integer -> Account -> STM ()
credit amount account = do
current <- readTVar account
writeTVar account (current + amount)
debit :: Integer -> Account -> STM ()
debit amount account = do
current <- readTVar account
writeTVar account (current - amount)
transfer :: Integer -> Account -> Account -> STM ()
transfer amount from to = do
debit amount from
credit amount to
रेतुर्न टाइप्स के STM ()
यह इंगित करने के लिए लिया जा सकता है कि हम ट्रांसक्शन्स के लिए स्क्रिप्ट बना रहे हैं। जब वास्तव में इस तरह के ट्रांसक्शन्स को निष्पादित करने का समय आता है, तो एक फ़ंक्शन atomically :: STM a -> IO a
प्रयोग किया जाता है। उपरोक्त कार्यान्वयन यह सुनिश्चित करेगा कि कोई अन्य ट्रांसक्शन्स उन चरों के साथ हस्तक्षेप नहीं कर रहा है जो इसे निष्पादित करते समय (से और तक) उपयोग कर रहे हैं, जिससे डेवलपर को यह सुनिश्चित करने की अनुमति मिलती है कि ऊपर की तरह दौड़ की स्थिति का सामना नहीं किया गया है। यह सुनिश्चित करने के लिए और सुधार किए जा सकते हैं कि सिस्टम में कुछ अन्य व्यावसायिक तर्क बनाए रखे जाते हैं, यानी ट्रांसक्शन्स को किसी खाते से तब तक पैसे लेने की कोशिश नहीं करनी चाहिए जब तक उसमें पर्याप्त पैसा न हो:
transfer :: Integer -> Account -> Account -> STM ()
transfer amount from to = do
fromVal <- readTVar from
if (fromVal - amount) >= 0
then do
debit amount from
credit amount to
else retry
यहां ही retry
फ़ंक्शन का उपयोग किया गया है, जो एक ट्रांसक्शन्स वापस करेगा और इसे फिर से प्रयास करेगा। एसटीएम में पुन: प्रयास करना स्मार्ट है क्योंकि यह ट्रांसक्शन्स को फिर से चलाने की कोशिश नहीं करेगा जब तक कि ट्रांसक्शन्स के दौरान संदर्भित चरों में से एक को किसी अन्य ट्रांसक्शन्स कोड द्वारा संशोधित नहीं किया गया हो। यह एसटीएम मोनड को काफी कुशल बनाता है।
ट्रांसफर फ़ंक्शन का उपयोग करने वाला एक उदाहरण प्रोग्राम इस तरह दिख सकता है:
module Main where
import Control.Concurrent (forkIO)
import Control.Concurrent.STM
import Control.Monad (forever)
import System.Exit (exitSuccess)
type Account = TVar Integer
main = do
bob <- newAccount 10000
jill <- newAccount 4000
repeatIO 2000 $ forkIO $ atomically $ transfer 1 bob jill
forever $ do
bobBalance <- atomically $ readTVar bob
jillBalance <- atomically $ readTVar jill
putStrLn ("Bob's balance: " ++ show bobBalance ++ ", Jill's balance: " ++ show jillBalance)
if bobBalance == 8000
then exitSuccess
else putStrLn "Trying again."
repeatIO :: Integer -> IO a -> IO a
repeatIO 1 m = m
repeatIO n m = m >> repeatIO (n - 1) m
newAccount :: Integer -> IO Account
newAccount amount = newTVarIO amount
transfer :: Integer -> Account -> Account -> STM ()
transfer amount from to = do
fromVal <- readTVar from
if (fromVal - amount) >= 0
then do
debit amount from
credit amount to
else retry
credit :: Integer -> Account -> STM ()
credit amount account = do
current <- readTVar account
writeTVar account (current + amount)
debit :: Integer -> Account -> STM ()
debit amount account = do
current <- readTVar account
writeTVar account (current - amount)
जिसे बॉब का बैलेंस: 8000, जिल का बैलेंस: 6000 प्रिंट करना चाहिए। यहां ही atomically
फ़ंक्शन का उपयोग IO मोनाड में STM क्रियाओं को चलाने के लिए किया गया है।
संदर्भ
- ↑ Simon Peyton Jones, Andrew D. Gordon, and Sigbjorn Finne. Concurrent Haskell. ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (PoPL). 1996. (Some sections are out of date with respect to the current implementation.)
- ↑ The Haskell Hierarchical Libraries, Control.Concurrent Archived 2012-08-02 at archive.today
- ↑ Tim Harris, Simon Marlow, Simon Peyton Jones, and Maurice Herlihy. Composable Memory Transactions. ACM Symposium on Principles and Practice of Parallel Programming 2005 (PPoPP'05). 2005.
- ↑ Control.Concurrent.STM