2.17

View in English

2.17 समवर्तीता और समानांतरता

अवलोकन और प्रेरणा

समवर्तीता किसी प्रोग्राम को स्वतंत्र कार्यों (tasks) के रूप में संरचित करने की कला है, जो एक-दूसरे की प्रतीक्षा किए बिना आगे बढ़ सकते हैं। समानांतरता वास्तव में उन कार्यों को एक ही क्षण में कई प्रोसेसरों पर निष्पादित करना है। यह अंतर पांडित्यपूर्ण नहीं है। समवर्तीता कोड को इस तरह व्यवस्थित करने का एक तरीका है कि एक धीमा नेटवर्क कॉल पूरे प्रोग्राम को रोक न दे; समानांतरता एक बड़ी गणना को कई कोर में फैलाकर तेज़ी से पूरा करने का तरीका है। इन दोनों को गड्डमड्ड करने से टीमें गति की आशा में थ्रेड जोड़ती हैं और उन्हें केवल बग मिलते हैं।

बड़ी टीमों के लिए यह विषय इसलिए महत्वपूर्ण है क्योंकि समवर्तीता वह जगह है जहाँ शुद्धता (correctness) चुपचाप समाप्त हो जाती है। एकल-थ्रेडेड कोड लिखने वाला एक लेखक उसे पंक्ति-दर-पंक्ति समझ सकता है, लेकिन जैसे ही कई लेखक थ्रेड्स के बीच मेमोरी साझा करते हैं, संभावित इंटरलीविंग की संख्या तेज़ी से बढ़ जाती है, और जो प्रोग्राम हर परीक्षण पास करता है वह उत्पादन भार के तहत दस लाख में एक बार विफल हो सकता है , किसी तेज़ क्रैश के साथ नहीं, बल्कि भ्रष्ट डेटा, अटके हुए रिक्वेस्ट, और ऐसी घटनाओं के साथ जिन्हें कोई पुनरुत्पादित नहीं कर सकता। यह अध्याय अध्याय 2.16 (प्रदर्शन इंजीनियरिंग) के कोड-स्तरीय फोकस और अध्याय 2.13 की कंप्यूटिंग नींव पर आधारित है, और यह अध्याय 3.3 (वितरित सिस्टम) की समन्वय समस्याओं में आगे बढ़ता है, जो मशीनों के बीच समवर्तीता है, साथ ही एक अविश्वसनीय नेटवर्क की अतिरिक्त क्रूरता के साथ।

उद्यमों (enterprises) के लिए, समवर्तीता बग थ्रूपुट बग हैं। उच्च-ट्रैफ़िक सेवाएँ साझा स्थिति (shared state) पर रेस किए बिना हज़ारों समवर्ती रिक्वेस्ट संभालने की अपनी क्षमता पर जीवित या मृत रहती हैं, और एक असिंक्रनाइज़्ड काउंटर भार के तहत एक लेजर को भ्रष्ट कर सकता है। सरकार के लिए, दांव उन सिस्टमों में शुद्धता और लेखापरीक्षा-योग्यता (auditability) का है जो दशकों तक चलते हैं और सुरक्षा, लाभों (benefits), या सार्वजनिक रिकॉर्ड को छूते हैं। कर या स्वास्थ्य प्रणाली में एक रेस असुविधा नहीं है; यह एक गलत उत्तर है जिसे किसी को बाद में एक निगरानी निकाय (oversight body) को समझाना पड़ता है। दोनों ही परिस्थितियों में लक्ष्य समान है: सुरक्षित पथ को डिफ़ॉल्ट बनाएं, ताकि कोड को छूने वाले कई लोगों में से हर एक को समवर्तीता विशेषज्ञ न बनना पड़े।

मुख्य सिद्धांत

  • समवर्तीता संरचना है; समानांतरता निष्पादन है। थ्रेड्स की ओर बढ़ने से पहले तय करें कि आपको वास्तव में किसकी आवश्यकता है।
  • साझा परिवर्तनशील स्थिति (shared mutable state) शत्रु है। लगभग हर समवर्तीता बग दो कार्यों के एक ही परिवर्तनशील डेटा को छूने से उत्पन्न होता है।
  • अपरिवर्तनीयता (immutability) और संदेश-पारेषण (message passing) को प्राथमिकता दें। जो डेटा बदल नहीं सकता, उस पर रेस नहीं हो सकती, और सुरक्षा के लिए संदेश साझा मेमोरी से बेहतर हैं।
  • अनिश्चयवाद (non-determinism) ही मूल कठिनाई है। हज़ार में एक बार दिखाई देने वाला बग ही असली समस्या है, कोई किनारे का मामला नहीं।
  • हर चीज़ की सीमा तय करें। असीमित कतारें (queues), थ्रेड काउंट, और चल रहा काम एक उछाल को आउटेज में बदल देते हैं।
  • उच्च-स्तरीय मॉडल कच्चे लॉक से बेहतर हैं। एक्टर्स, चैनल, और संरचित समवर्तीता (structured concurrency) कई लेखकों को एक सुरक्षित डिफ़ॉल्ट देते हैं, और हर लॉक की एक कीमत होती है।
  • इंटरलीविंग का परीक्षण करें, न कि केवल हैप्पी पाथ का। निर्धारक (deterministic) परीक्षण उस बग को नहीं पकड़ सकते जो केवल एक दुर्लभ क्रम में प्रकट होता है।

अनुशंसाएँ

तय करें कि आपको समवर्तीता चाहिए या समानांतरता

समस्या को नाम देकर शुरुआत करें। यदि आपकी सेवा अपना अधिकांश समय प्रतीक्षा में बिताती है (डेटाबेस, नेटवर्क कॉल, या डिस्क पर), तो आपके पास I/O-बाउंड वर्कलोड है, और समवर्तीता ही उत्तर है: कोड को इस तरह संरचित करें कि जब एक रिक्वेस्ट प्रतीक्षा करे, तो दूसरे आगे बढ़ें। async/await के साथ एक ही थ्रेड, या एक छोटा पूल, हज़ारों प्रतीक्षारत रिक्वेस्ट संभाल सकता है। यदि इसके बजाय आपका प्रोग्राम CPU-बाउंड है, थोड़ी प्रतीक्षा के साथ गणना में व्यस्त है, तो कोर के बीच समानांतरता ही गति खरीदती है, और यहाँ सीमा एम्डाल के नियम (Amdahl’s law) (अध्याय 2.16 देखें) द्वारा तय होती है: क्रमिक (serial) भाग आपके गति-लाभ (speedup) को सीमित कर देता है, चाहे आप कितने भी कोर जोड़ें। डिज़ाइन करने से पहले मापें कि आप किस स्थिति में हैं।

साझा परिवर्तनशील स्थिति को शत्रु मानें

लगभग हर समवर्तीता दोष एक ही आकार में सिमट जाता है: दो कार्य एक क्रम पर सहमत हुए बिना एक ही परिवर्तनशील डेटा को पढ़ते और लिखते हैं। यह एक रेस कंडीशन है, और यह खोए हुए अपडेट, आधे-लिखे ऑब्जेक्ट, और ऐसे मान उत्पन्न करती है जो उन अपरिवर्तनीयताओं (invariants) का उल्लंघन करते हैं जिन्हें कोड सुरक्षित मानता था। सबसे विश्वसनीय बचाव है कम साझा परिवर्तनशील स्थिति रखना। हर कार्य को अपना डेटा दें, संदर्भों (references) के बजाय प्रतियां (copies) पास करें, और परिवर्तनशील स्थिति को एक ही स्वामी तक सीमित रखें जिस तक बाकी संदेशों के माध्यम से पहुँचें। जब आपको वास्तव में साझा करना ही पड़े, तो साझाकरण को स्पष्ट और छोटा रखें, ताकि एक समीक्षक उस स्थिति को छूने वाली हर जगह देख सके।

डिफ़ॉल्ट के रूप में अपरिवर्तनीयता और संदेश-पारेषण को प्राथमिकता दें

सबसे सुरक्षित साझा डेटा वह है जो बदल नहीं सकता। एक बार निर्मित होने पर, एक अपरिवर्तनीय ऑब्जेक्ट को किसी भी संख्या में थ्रेड्स द्वारा शून्य सिंक्रनाइज़ेशन के साथ पढ़ा जा सकता है, क्योंकि उस पर रेस करने के लिए कुछ है ही नहीं। अपरिवर्तनीयता को अपना डिफ़ॉल्ट और परिवर्तनशीलता को जानबूझकर किया गया अपवाद बनाएं। जब कार्यों को समन्वय करना ही पड़े, तो साझा मेमोरी की बजाय संदेश-पारेषण को प्राथमिकता दें: एक साझा चर (variable) साझा करने के बजाय, एक कार्य दूसरे को मान भेजे, जो Go की इस कहावत के पीछे का दर्शन है: “मेमोरी साझा करके संवाद न करें; संवाद करके मेमोरी साझा करें” (do not communicate by sharing memory; share memory by communicating)। संदेश-पारेषण अदृश्य, क्रम-निर्भर बगों को स्पष्ट, निरीक्षण-योग्य डेटा प्रवाह में बदल देता है, और यह स्पष्टता उस कोड में लगभग हमेशा प्रति-संदेश लागत के लायक होती है जिसे कई लोग बनाए रखते हैं।

कच्चे लॉक से पहले उच्च-स्तरीय मॉडल की ओर बढ़ें

हाथ से लिखी लॉकिंग सिद्धांत रूप में सही और व्यवहार में विनाशकारी होती है, क्योंकि मनुष्य हर इंटरलीविंग के बारे में तर्क करने में खराब हैं। ऐसे मॉडल को प्राथमिकता दें जो सुरक्षित समवर्तीता को डिफ़ॉल्ट बनाते हैं। एक्टर मॉडल हर एक्टर को निजी स्थिति और एक मेलबॉक्स देता है: एक्टर कभी मेमोरी साझा नहीं करते और केवल संदेश भेजते हैं, इसलिए रेस की पूरी श्रेणियाँ ही गायब हो जाती हैं। कम्युनिकेटिंग सीक्वेंशियल प्रोसेसेस (CSP), जो Go जैसी भाषाओं में चैनलों के पीछे का मॉडल है, स्वतंत्र प्रोसेस को टाइप किए गए चैनलों पर मान पास करने देता है। संरचित समवर्तीता समवर्ती कार्यों के जीवनकाल को एक लेक्सिकल स्कोप से बांधती है, ताकि कार्य उस ब्लॉक से अधिक जीवित न रहें जिसने उन्हें उत्पन्न किया, और त्रुटियाँ गायब होने के बजाय प्रसारित हों। Async/await आपको क्रमिक शैली में समवर्ती, I/O-बाउंड कोड लिखने देता है। इनमें से हर एक औसत लेखक के लिए फर्श ऊंचा उठाता है, जो एक बड़ी टीम को चाहिए होता है।

अपने मेमोरी मॉडल, परमाणुता, और दृश्यता को समझें

जब आप वास्तव में मेमोरी साझा करते हैं, तो दो गुण काटते हैं। परमाणुता (atomicity) का अर्थ है कि एक ऑपरेशन या तो पूरी तरह होता है या बिल्कुल नहीं होता; एक साधारण वृद्धि (x = x + 1) परमाणु नहीं है, क्योंकि यह तीन चरणों ( पढ़ना, जोड़ना, और लिखना ) के रूप में होती है, जिसे दूसरा थ्रेड बाधित कर सकता है, और इसी तरह काउंटर अपडेट खो देते हैं। दृश्यता (visibility) का अर्थ है कि एक थ्रेड द्वारा किया गया लेखन दूसरे को अवलोकनीय हो जाता है; उचित सिंक्रनाइज़ेशन के बिना, एक कोर पर लिखा गया मान कैश में बैठा रह सकता है जिसे दूसरा कोर नहीं देखता, इसलिए एक थ्रेड उस फ्लैग पर हमेशा के लिए लूप कर सकता है जो पहले ही सेट हो चुका था। आपकी भाषा का मेमोरी मॉडल यह परिभाषित करता है कि लेखन कब दृश्यमान होते हैं और कंपाइलर तथा CPU किन क्रमों को पुनर्व्यवस्थित कर सकते हैं, इसलिए आप यह नहीं मान सकते कि कोड उसी क्रम में चलता है जिस क्रम में आपने उसे लिखा था। अपनी खुद की लॉक-फ्री योजना बनाने के बजाय भाषा के परमाणु प्रकारों (atomic types) और सिंक्रनाइज़ेशन प्रिमिटिव का उपयोग करें।

सिंक्रनाइज़ेशन प्रिमिटिव का जानबूझकर उपयोग करें, और डेडलॉक के विरुद्ध डिज़ाइन करें

जब साझाकरण अपरिहार्य हो, तो सही प्रिमिटिव की ओर बढ़ें और उसकी कीमत का सम्मान करें। एक लॉक या म्यूटेक्स (पारस्परिक बहिष्करण) एक समय में एक थ्रेड को क्रिटिकल सेक्शन में प्रवेश करने देता है, लेकिन पहुँच को क्रमबद्ध कर देता है, इसलिए एक गर्म लॉक एक बॉटलनेक बन जाता है जो कई कोर के लाभ को मिटा देता है। एक सेमाफोर यह सीमित करता है कि एक साथ कितने कार्य आगे बढ़ सकते हैं, जो एक पूल को सीमित करने का तरीका है। परमाणु ऑपरेशन काउंटर जैसे सरल मानों के लिए लॉक-फ्री अपडेट प्रदान करते हैं, जो लॉक से सस्ते हैं लेकिन किसी भी संयुक्त (compound) चीज़ के लिए दुरुपयोग करना आसान है। लॉक तीन क्लासिक विफलता मोड लाते हैं। एक डेडलॉक तब होता है जब कार्य एक चक्र में एक-दूसरे की प्रतीक्षा करते हैं और कोई भी आगे नहीं बढ़ सकता, पाठ्यपुस्तक का मामला है दो थ्रेड जिनमें से हर एक एक लॉक पकड़े है और दूसरा चाहता है। एक लाइवलॉक (livelock) तब होता है जब कार्य एक-दूसरे पर प्रतिक्रिया करते रहते हैं लेकिन कोई प्रगति नहीं करते। भुखमरी (starvation) तब होती है जब एक कार्य को कभी संसाधन नहीं मिलता क्योंकि बाकी लगातार आगे कूदते रहते हैं। इन्हें रोकने वाले अनुशासन ठोस हैं: एक वैश्विक लॉक क्रम (global lock ordering) लागू करें, लॉक को संक्षिप्त रूप से पकड़ें, टाइमआउट जोड़ें ताकि एक अटका हुआ कार्य ज़ोर से विफल हो, लॉक पकड़े हुए कभी अज्ञात कोड को कॉल न करें, और जहां भुखमरी जोखिम हो वहां निष्पक्ष शेड्यूलिंग का उपयोग करें। इन नियमों को लिख लें, क्योंकि एक नया लेखक इन्हें अकेले कोड से पुनः खोज नहीं सकता।

बैकप्रेशर के साथ अपनी कतारों, पूल, और चल रहे काम की सीमा तय करें

एक असीमित कतार एक टाइम बम है। ट्रैफ़िक उछाल के तहत, काम उससे तेज़ी से आता है जितनी तेज़ी से वह निकलता है, कतार असीमित रूप से बढ़ती है, मेमोरी भर जाती है, और सेवा एक ऐसे तरीके से मरती है जो अधिभार (overload) के बजाय एक रहस्यमयी आउट-ऑफ-मेमोरी क्रैश जैसा दिखता है। हर कतार की सीमा तय करें, हर थ्रेड पूल को सीमित करें, और बैकप्रेशर लागू करें: जब सिस्टम भर जाए, तो अनंत काम स्वीकार करने के बजाय जिसे आप पूरा नहीं कर सकते, अपस्ट्रीम को धीमा होने या काम को तेज़ी से अस्वीकार करने का संकेत दें। पूल को वर्कलोड के अनुसार आकार दें (CPU-बाउंड काम के लिए लगभग कोर काउंट के बराबर, I/O-बाउंड काम के लिए अधिक जहां थ्रेड ज़्यादातर प्रतीक्षा करते हैं), और सीमा को एक जानबूझकर की गई क्षमता निर्णय के रूप में मानें। यह अध्याय 3.3 के लचीलापन (resilience) पैटर्न से जुड़ता है।

जहां काम शर्मनाक रूप से समानांतर (embarrassingly parallel) हो, वहां डेटा समानांतरता का उपयोग करें

कुछ समस्याएं स्पष्ट रूप से विभाजित होती हैं: एक बड़े डेटासेट के हर तत्व पर वही ऑपरेशन लागू करें, जिसमें कोई तत्व दूसरे पर निर्भर न हो। यह डेटा समानांतरता सबसे अनुकूल प्रकार है, क्योंकि रेस करने के लिए बहुत कम साझा स्थिति होती है और गति-लाभ कोर काउंट के करीब पहुंच सकता है, जैसा कि मैप-रिड्यूस पाइपलाइन, समानांतर ऐरे ऑपरेशन, और वेक्टराइज़्ड न्यूमेरिक कोड सभी दिखाते हैं। यहां भी, एम्डाल के नियम का सम्मान करें: मर्ज या रिड्यूस चरण अक्सर क्रमिक (serial) होता है और आपके लाभ को सीमित करता है, और छोटे इनपुट के लिए विभाजन का ओवरहेड हावी हो सकता है। इसकी ओर तब बढ़ें जब प्रति-तत्व काम पर्याप्त हो और तत्व वास्तव में स्वतंत्र हों; अन्यथा सबसे सरल क्रमिक संस्करण अक्सर पर्याप्त तेज़ और सही बनाए रखने में कहीं अधिक आसान दोनों होता है, एक बिंदु जिसे अध्याय 2.9 की निर्माण प्रथाएं (construction practices) पुष्ट करती हैं।

जानबूझकर अनिश्चयात्मक कोड का परीक्षण और डिबगिंग करें

समवर्तीता बग अनिश्चयात्मक होते हैं, इसलिए साधारण परीक्षण, जो एक इंटरलीविंग चलाते हैं, उन्हें अधिकतर चूक जाते हैं। दुर्लभ क्रमों को हिलाने के लिए रैंडमाइज़्ड टाइमिंग के तहत कई कार्य चलाने वाले स्ट्रेस और फज़ परीक्षणों के साथ जानबूझकर समस्या पर हमला करें। रेस डिटेक्टर और थ्रेड सैनिटाइज़र की ओर बढ़ें, ऐसे टूलिंग जो मेमोरी एक्सेस को इंस्ट्रूमेंट करते हैं ताकि डेटा रेस पकड़ी जा सके भले ही बगयुक्त इंटरलीविंग इस बार न हुई हो। जहां आपका प्लेटफ़ॉर्म इसे प्रदान करे, निश्चयात्मक सिमुलेशन या नियंत्रित शेड्यूलर का उपयोग करें जो एक विशिष्ट इंटरलीविंग को दोबारा चलाते हैं, एक हाइज़ेनबग (heisenbug) को पुनरुत्पादन-योग्य बग में बदलते हुए, और इस तरह डिज़ाइन करें कि उत्पादन में एक हैंग आपको थ्रेड स्थितियां और लॉक स्वामित्व कैप्चर करने दे, जो अध्याय 2.15 के डिबगिंग अनुशासन से जुड़ता है। सबसे ऊपर, ऐसे डिज़ाइन (अपरिवर्तनीयता, संदेश-पारेषण, एकल स्वामित्व) को प्राथमिकता दें जो इन बगों की पूरी श्रेणियों को असंभव बना दें, क्योंकि जो बग आप बना ही नहीं सकते, वह कभी डिबग नहीं करना पड़ता।

समझौते: फायदे और नुकसान

दृष्टिकोणफायदेनुकसान
लॉक के साथ साझा मेमोरीप्रति ऑपरेशन तेज़; परिचितरेस, डेडलॉक, और दृश्यता बग; कई लेखकों के लिए सही बनाए रखना कठिन
अपरिवर्तनीयताकोई सिंक्रनाइज़ेशन आवश्यक नहीं; सहजता से थ्रेड-सेफ रीडकॉपी करने की लागत; बड़ी परिवर्तनशील संरचनाओं के लिए असुविधाजनक
संदेश-पारेषण (एक्टर, चैनल)स्पष्ट डेटा प्रवाह; बग की पूरी श्रेणियाँ गायबप्रति-संदेश ओवरहेड; यदि कतारें असीमित हों तो बैकप्रेशर छिपा सकता है
Async/awaitI/O-बाउंड काम के लिए सस्ती समवर्तीता; क्रमिक दिखने वाला कोडCPU काम के लिए कोई समानांतरता नहीं; एक कार्य को ब्लॉक करना बाकियों को रोकता है
संरचित समवर्तीतास्पष्ट कार्य जीवनकाल; त्रुटियां प्रसारित होती हैं; कोई लीक कार्य नहींनई तकनीक, कुछ इकोसिस्टम में कम उपलब्ध
डेटा समानांतरतास्वतंत्र काम पर लगभग-रैखिक गति-लाभएम्डाल सीमा; छोटे इनपुट पर ओवरहेड हावी
परमाणु / लॉक-फ्रीसरल मानों के लिए कोई लॉक प्रतिस्पर्धा नहींसूक्ष्म रूप से गलत होना बेहद आसान; समीक्षा करना कठिन

केंद्रीय तनाव सुरक्षा बनाम कच्ची गति का है, और समाधान है पहले शुद्धता खरीदना और प्रदर्शन तभी खर्च करना जब माप यह साबित करे कि आपको उसकी ज़रूरत है। कच्ची साझा-मेमोरी लॉकिंग प्रति ऑपरेशन सबसे तेज़ और प्रति पंक्ति कोड सबसे खतरनाक है; उच्च-स्तरीय मॉडल थोड़ा थ्रूपुट खर्च करते हैं और बदले में काफ़ी सुरक्षा और स्पष्टता देते हैं, और कई हाथों द्वारा बनाए रखे गए कोड के लिए यह सौदा करना निर्णायक रूप से उचित है। हाथ से ट्यून की गई लॉक-फ्री समवर्तीता को उन छोटे गर्म बिंदुओं के लिए आरक्षित रखें जहां एक प्रोफाइलर (अध्याय 2.16) साबित करे कि समन्वय ओवरहेड मायने रखता है, और उन्हें भी एक अच्छी तरह परीक्षित सीमा के पीछे रखें।

अपनी टीम के साथ चर्चा करने योग्य प्रश्न

  1. आपकी सबसे व्यस्त सेवा के लिए, क्या वर्कलोड I/O-बाउंड है या CPU-बाउंड, और क्या आपका समवर्तीता डिज़ाइन उससे मेल खाता है? टीमें नियमित रूप से उन सेवाओं में थ्रेड पूल जोड़ती हैं जो अपना 95% समय डेटाबेस की प्रतीक्षा में बिताती हैं, जिससे प्रतिस्पर्धा तो मिलती है पर थ्रूपुट नहीं, या वे एक ऐसी गणना को समानांतर करने की कोशिश करती हैं जिसका क्रमिक भाग किसी भी गति-लाभ को सीमित कर देता है। सही डिज़ाइन उस स्थिति से निकलता है: प्रतीक्षा-भारी काम के लिए async या एक छोटा पूल, गणना-भारी काम के लिए कोर के बीच वास्तविक समानांतरता। एक धारणा नहीं, बल्कि एक प्रोफ़ाइल लाएं जो दिखाए कि समय वास्तव में कहां जाता है, और यदि अधिकांश समय गणना में जाता है, तो क्रमिक भाग को मापें और एम्डाल का नियम आपको सीमा बताने दें। उत्तर तय करता है कि आप async, एक सीमित पूल, या डेटा समानांतरता की ओर बढ़ते हैं।

  2. कार्यों के बीच स्थिति साझा करने के लिए आपकी टीम का डिफ़ॉल्ट क्या है, और क्या यह निर्माण द्वारा (by construction) सुरक्षित है? एक बड़ी टीम में, अपवादों से ज़्यादा डिफ़ॉल्ट मायने रखता है, क्योंकि अधिकांश कोड उन लोगों द्वारा लिखा जाता है जो समवर्तीता विशेषज्ञ नहीं हैं और जो जो भी पैटर्न पहले से मौजूद है उसकी नकल करते हैं। यदि डिफ़ॉल्ट तदर्थ (ad hoc) लॉक से सुरक्षित साझा परिवर्तनशील ऑब्जेक्ट है, तो आप एक भूले हुए लॉक की दूरी पर हैं उस रेस से जो महीनों बाद उत्पादन में सामने आएगी। यदि डिफ़ॉल्ट अपरिवर्तनीयता और संदेश-पारेषण है, तो बग की पूरी श्रेणियाँ कभी होती ही नहीं, और वह दुर्लभ जगह जिसे वास्तव में साझा मेमोरी चाहिए, सावधानीपूर्वक समीक्षा के लिए अलग दिखती है। चर्चा करें कि आज एक नया इंजीनियर किस चीज़ की ओर बढ़ेगा, क्या आपकी समीक्षाएं एक असिंक्रनाइज़्ड लेखन पकड़ लेंगी, और सुरक्षित पथ को आसान कैसे बनाया जाए।

  3. आप उस समवर्तीता बग को कैसे ढूंढेंगे, पुनरुत्पादित करेंगे, और ठीक करेंगे जो उत्पादन में दस लाख रिक्वेस्ट में एक बार दिखाई देता है? कई टीमों के लिए ईमानदार उत्तर यह है कि वे नहीं कर पाएंगी, क्योंकि जब वे देखती हैं तो बग गायब हो जाता है और उनके परीक्षण हमेशा केवल एक निरापद इंटरलीविंग चलाते हैं। इससे आपको चिंतित होना चाहिए, क्योंकि ये बग चुपचाप डेटा भ्रष्ट करते हैं और विश्वास को नष्ट करते हैं। बात करें कि क्या आप निरंतर एकीकरण (continuous integration) में रेस डिटेक्टर और थ्रेड सैनिटाइज़र चलाते हैं, क्या आप रैंडमाइज़्ड टाइमिंग के साथ स्ट्रेस टेस्ट करते हैं, और क्या आपकी उत्पादन ऑब्ज़र्वेबिलिटी एक हैंग के क्षण में थ्रेड और लॉक स्थिति कैप्चर करती है। सबसे अच्छी टीमें अपने मॉडल की पसंद के माध्यम से अधिकांश ऐसे बग को असंभव बनाकर उत्तर देती हैं, ताकि बचे हुए कुछ दुर्लभ और सीमित हों।

  4. आपके सिस्टम में कहां अभी भी एक असीमित कतार या एक असीमित थ्रेड पूल मौजूद है, और अचानक दस गुना उछाल के तहत उसका क्या होता है? यह इसलिए मायने रखता है क्योंकि असीमित चल रहा काम वह विफलता है जो रहस्यमयी आउट-ऑफ-मेमोरी क्रैश के रूप में सामने आती है: काम निकलने से तेज़ी से आता है, मेमोरी भर जाती है, और सेवा एक हार्डवेयर दोष जैसी दिखते हुए मरती है, न कि उस अधिभार जैसी जो वह वास्तव में है। प्रतिस्पर्धी विचार वास्तविक हैं, क्योंकि बहुत कम सेट की गई सीमा वैध ट्रैफ़िक को अस्वीकार करती है और बहुत ऊंची सीमा क्रैश को रोकने के बजाय टाल देती है, इसलिए यह संख्या एक अनुमान नहीं बल्कि एक क्षमता निर्णय है। हर कतार और पूल की एक सूची लाएं, उसकी वर्तमान सीमा (या यह स्वीकृति कि उसकी कोई सीमा नहीं है), भरने पर बैकप्रेशर व्यवहार, और किनारे पर सिस्टम कैसे गिरावट करता है इसका लोड-टेस्ट प्रमाण। एक उद्यम बेड़े (fleet) के लिए एक भी असीमित कतार बेड़े-व्यापी आउटेज में बदल सकती है, और एक सरकारी प्लेटफ़ॉर्म के लिए जिसे नागरिकों के लिए उपलब्ध रहना चाहिए, स्पष्ट त्रुटि के साथ शालीन अस्वीकृति एक सेवा दायित्व है, इसलिए सीमा और उसका अस्वीकृति पथ क्षमता योजना और रनबुक में होना चाहिए, न कि किसी एक इंजीनियर की याददाश्त में।

  5. उच्च-स्तरीय समवर्तीता मॉडल बनाम हाथ से लिखे लॉक के उपयोग के लिए आपकी टीम की नीति क्या है, और आपने कहां अपवाद दिए हैं? डिफ़ॉल्ट मॉडल यह तय करता है कि औसत परिवर्तन कितना सुरक्षित है, क्योंकि अधिकांश लेखक समवर्तीता विशेषज्ञ नहीं हैं और जो भी पैटर्न पहले से मौजूद है उसकी नकल करेंगे: एक्टर, चैनल, और संरचित समवर्तीता सबके लिए फर्श ऊंचा उठाते हैं, जबकि कच्ची लॉकिंग सिद्धांत में सही और व्यवहार में डेडलॉक का स्रोत है। तनाव यह है कि उच्च-स्तरीय मॉडल प्रति-संदेश या प्रति-कार्य थोड़ा ओवरहेड खर्च करते हैं, और एक प्रोफाइलर कभी-कभी साबित करेगा कि एक गर्म पथ को हाथ से ट्यून किए गए लॉक-फ्री कोड की ज़रूरत है, इसलिए एक सर्वव्यापी प्रतिबंध उतना ही गलत है जितनी एक खुली छूट। सुरक्षित डिफ़ॉल्ट से नीचे जाने वाली जगहों की सूची लाएं, हर एक को उचित ठहराने वाला प्रोफाइलिंग प्रमाण, और हर अपवाद को एक परीक्षित सीमा और दस्तावेज़ीकृत लॉक क्रम के पीछे कैसे बाड़ लगाया गया है। एक बड़े उद्यम में यह नीति ही है जो हज़ारों योगदानकर्ताओं को हर किसी को अपनी असुरक्षित योजना फिर से आविष्कार करने से रोकती है, और एक दीर्घकालिक सरकारी सिस्टम में यह वही है जो एक समीक्षक को वर्षों बाद यह समझने देता है कि एक खतरनाक पैटर्न की अनुमति क्यों दी गई और यह पुष्टि करने देता है कि वह अभी भी उचित है।

  6. जब आप किसी गणना को समानांतर करने का निर्णय लेते हैं, तो आप क्रमिक भाग (serial fraction) कैसे मापते हैं, और गति-लाभ वास्तविक होने की पुष्टि करने के लिए कौन जवाबदेह है? टीमें नियमित रूप से एक गणना को कोर में फैलाती हैं और एक ऐसी संख्या का जश्न मनाती हैं जिसकी पुष्टि एक प्रोफाइलर कभी नहीं करेगा, क्योंकि एम्डाल का नियम लाभ को क्रमिक भाग के व्युत्क्रम पर सीमित करता है चाहे आप कितने भी कोर जोड़ें, और विभाजन-और-मर्ज ओवरहेड छोटे इनपुट के लिए लाभ को पूरी तरह मिटा सकता है। प्रतिस्पर्धी खिंचाव यह है कि समानांतरता वास्तविक जटिलता और नया रेस सतह जोड़ती है, इसलिए सवाल यह है कि क्या मापा गया गति-लाभ उस शुद्धता जोखिम को उचित ठहराता है जिसे आप उठा रहे हैं। एक प्रोफ़ाइल लाएं जो क्रमिक हिस्से को अलग करे, वे इनपुट आकार जहां समानांतरता वास्तव में जीतती है, और एक आशावादी अनुमान के बजाय प्रतिनिधि हार्डवेयर पर पहले-और-बाद का बेंचमार्क। एक बड़े कंप्यूट बेड़े के लिए भुगतान करने वाले उद्यम के लिए, एक ईमानदार क्रमिक-भाग विश्लेषण बचाए गए या बर्बाद हुए हार्डवेयर खर्च में बदल जाता है, और एक सार्वजनिक सिस्टम की लागत के लिए जवाबदेह सरकारी निकाय के लिए, समानांतर डिज़ाइन को मंज़ूरी देने वाले व्यक्ति को लेखा-परीक्षा (audit) के तहत वह माप दिखाने में सक्षम होना चाहिए जिसने उसे उचित ठहराया।

क्षेत्रीय दृष्टिकोण

स्टार्टअप। एक छोटी टीम और गंवाने के लिए कोई फुर्सत नहीं होने पर, संरचना से शुद्धता खरीदें, न कि किसी समवर्तीता विशेषज्ञ से जिसे आप नियुक्त नहीं कर सकते। अपनी भाषा द्वारा दिए गए एकल सुरक्षित डिफ़ॉल्ट की ओर बढ़ें, I/O-बाउंड काम के लिए async/await, किसी भी साझा स्थिति के लिए एक स्वामी कार्य या एक एक्टर, और हाथ से ट्यून की गई लॉकिंग को पूरी तरह छोड़ें। भुगतान पथ में एक खोया-अपडेट रेस आपको किसी छूटी हुई सुविधा से भी तेज़ी से डुबो सकती है, इसलिए बग की उस श्रेणी को असंभव बनाने के लिए थोड़ा अतिरिक्त कोड खर्च करें और आगे बढ़ें।

लघु व्यवसाय। आपके पास कोई ऐसा व्यक्ति नहीं है जिसका काम समवर्तीता हो, इसलिए ऐसे प्लेटफ़ॉर्म और प्रबंधित सेवाओं को प्राथमिकता दें जो इसे आपके लिए संभालें: एक डेटाबेस ट्रांज़ैक्शन, एक होस्ट की गई कतार, या एक फ्रेमवर्क का रिक्वेस्ट मॉडल हाथ से बनाए रखे गए थ्रेड से बेहतर है। जब आप किसी टूल का मूल्यांकन करें, तो “क्या यह समवर्तीता को डिफ़ॉल्ट रूप से सुरक्षित बनाता है” को खरीदने-बनाम-बनाने के प्रश्न के रूप में लें, और उस विकल्प को प्राथमिकता दें जहां एक गलत इंटरलीविंग किसी ग्राहक के रिकॉर्ड को चुपचाप भ्रष्ट नहीं कर सकती। जहां भी एक सीमित, प्रबंधित सेवा इसे संभाल सकती है, वहां साझा परिवर्तनशील स्थिति को अपने कोड से बाहर रखें।

उद्यम। कई टीमों में लक्ष्य एक घरेलू डिफ़ॉल्ट है जो हज़ारों योगदानकर्ताओं को सुरक्षित रखे: मानदंड के रूप में अपरिवर्तनीयता और संदेश-पारेषण, कच्चे लॉक की तुलना में उच्च-स्तरीय मॉडल, बैकप्रेशर के साथ सीमित कतारें और पूल, और एक दस्तावेज़ीकृत वैश्विक लॉक क्रम। इन्हें इंजीनियरिंग मानकों में एन्कोड करें, उन्हें CI में रेस डिटेक्टर और स्ट्रेस टेस्ट के साथ लागू करें, और उन अपवादों को गवर्न करें जहां एक प्रोफाइलर ने लॉक-फ्री कोड को उचित ठहराया ताकि हर एक एक परीक्षित, समीक्षित सीमा के पीछे बना रहे। मापे गए भार से जुड़ी कतार सीमाओं और पूल आकारों के साथ समवर्तीता क्षमता को एक बेड़े-व्यापी चिंता के रूप में प्रबंधित करें।

सरकार। दशकों तक चलने वाले सिस्टमों में शुद्धता और लेखापरीक्षा-योग्यता कच्चे थ्रूपुट से ऊपर हैं। मांग करें कि हर स्थिति परिवर्तन दर्ज और पुनः चलाने-योग्य हो, ताकि एक संदिग्ध रेस को पुनरुत्पादित किया जा सके और एक निगरानी निकाय को समाधान साबित किया जा सके, और लाभों, सुरक्षा, या सार्वजनिक रिकॉर्ड को छूने वाले निर्णयों के लिए AI-मुक्त निश्चयात्मक पथ बनाए रखें। खरीद प्रक्रिया को यह अनिवार्य करना चाहिए कि विक्रेता अपने समवर्तीता मॉडल और रेस-डिटेक्टर तथा स्ट्रेस-टेस्ट कवरेज के प्रमाण का खुलासा करें, क्योंकि एक सार्वजनिक सिस्टम में भार के तहत एक गलत उत्तर कोई असुविधा नहीं है, यह कुछ ऐसा है जिसे एक जवाबदेह अधिकारी को बाद में समझाना पड़ता है।

उदाहरण

स्टार्टअप। एक छोटी टीम एक भुगतान सुविधा जारी करती है और पाती है कि भार के तहत खाता शेष कभी-कभी कुछ सेंट से बहक जाता है। इसका कारण समवर्ती रिक्वेस्ट हैंडलरों से एक शेष क्षेत्र पर एक साधारण पढ़ो-संशोधित करो-लिखो है, एक खोया-अपडेट रेस। लॉक बिखेरने के बजाय, वे हर खाते के शेष को एक ही स्वामी कार्य के पीछे ले जाते हैं जो डेबिट और क्रेडिट को संदेशों के रूप में, एक-एक करके, प्रोसेस करता है। बहकना गायब हो जाता है, कोड के बारे में तर्क करना आसान हो जाता है, और वे एक स्ट्रेस टेस्ट जोड़ते हैं जो समाधान की रक्षा के लिए हज़ारों समवर्ती ट्रांसफ़र दागती है। एक संरचनात्मक परिवर्तन, बग की एक पूरी श्रेणी सेवानिवृत्त।

उद्यम। प्रति सेकंड दसियों हज़ार रिक्वेस्ट संभालने वाली एक उच्च-थ्रूपुट ऑर्डर सेवा ट्रैफ़िक उछाल के दौरान आवधिक विलंबता (latency) उछाल और कभी-कभी आउट-ऑफ-मेमोरी क्रैश से पीड़ित होती है। जांच से एक थ्रेड पूल के पीछे एक असीमित काम कतार मिलती है जो मांग क्षमता से अधिक होने पर असीमित रूप से बढ़ती है। टीम कतार को सीमित करती है, पूल को कोर काउंट से जुड़े आकार पर सीमित करती है, और बैकप्रेशर जोड़ती है जो एक स्पष्ट त्रुटि के साथ अतिरिक्त भार को तेज़ी से अस्वीकार करती है। थ्रूपुट पूर्वानुमेय हो जाता है, क्रैश रुक जाते हैं, और एक साझा कैश पर एक गर्म लॉक को लॉक-फ्री संरचना से तभी बदला जाता है जब एक प्रोफाइलर साबित करता है कि प्रतिस्पर्धा वास्तविक है। कई लेखकों के लिए सुरक्षित डिफ़ॉल्ट, ट्यून की गई समवर्तीता केवल वहां जहां मापी गई हो।

सरकार। एक राष्ट्रीय लाभ (benefits) प्लेटफ़ॉर्म दशकों तक चलता है और समवर्ती केस अपडेट के तहत भी लेखापरीक्षा-योग्य, सही परिणाम उत्पन्न करना चाहिए। टीम घरेलू डिफ़ॉल्ट के रूप में अपरिवर्तनीयता और संदेश-पारेषण चुनती है, परिवर्तनशील स्थिति के हर टुकड़े को एक ही स्वामी तक सीमित करती है, और जहां भी लॉक बने रहते हैं वहां एक वैश्विक लॉक क्रम लागू करती है, यह सब इंजीनियरिंग मानकों में लिखा गया है। वे पाइपलाइन में थ्रेड सैनिटाइज़र और रैंडमाइज़्ड स्ट्रेस टेस्ट चलाते हैं, और इस तरह डिज़ाइन करते हैं कि हर स्थिति परिवर्तन निगरानी के लिए दर्ज और पुनः चलाने-योग्य हो, जो उन्हें एक दुर्लभ इंटरलीविंग संदिग्ध होने पर समाधान को पुनरुत्पादित और साबित करने देता है। शुद्धता और लेखापरीक्षा-योग्यता को प्रथम-श्रेणी की आवश्यकताओं के रूप में माना जाता है, प्रदर्शन के बाद के विचार के रूप में नहीं।

व्यवसाय मामला: प्रेरणाएं, ROI, और TCO

अनुशासित समवर्तीता पर रिटर्न उन घटनाओं के रूप में दिखाई देता है जो कभी नहीं होतीं। एक ही उत्पादन रेस हज़ारों रिकॉर्ड में डेटा भ्रष्ट कर सकती है, और लागत में उस बग को ढूंढने के इंजीनियरिंग घंटे शामिल हैं जो देखे जाने पर छिप जाता है, साथ ही खराब डेटा को समेटने, प्रभावित उपयोगकर्ताओं को सूचित करने, और विश्वास फिर से बनाने की कहीं बड़ी लागत भी। ये निदान करने के लिए सबसे महंगे दोषों में से हैं ठीक इसलिए क्योंकि वे अनिश्चयात्मक हैं, इसलिए एक हाइज़ेनबग का पीछा करना अग्रिम में एक सुरक्षित मॉडल चुनने के प्रयास को बौना बना सकता है।

लाभ थ्रूपुट और लागत के रूप में भी दिखाई देता है। समवर्तीता को सही आकार देना एक सेवा को उसी हार्डवेयर पर कहीं अधिक भार संभालने देता है, एक बड़े बेड़े के लिए एक बार-बार होने वाली बचत, जबकि बैकप्रेशर और सीमित कतारें उन कैस्केडिंग आउटेज को रोकती हैं जो एक ट्रैफ़िक उछाल को एक सार्वजनिक घटना में बदल देते हैं। कुल स्वामित्व लागत (TCO) मामूली और ज़्यादातर सांस्कृतिक है: आप एक घरेलू शैली (अपरिवर्तनीयता, संदेश-पारेषण, संरचित समवर्तीता) में, टूलिंग (रेस डिटेक्टर, थ्रेड सैनिटाइज़र, CI में स्ट्रेस हार्नेस) में, और लॉक क्रम व सीमा को एन्कोड करने वाले मानकों में निवेश करते हैं। विकल्प एक ऐसा कोडबेस है जहां शुद्धता इस पर निर्भर करती है कि हर लेखक हमेशा के लिए विशेषज्ञ रहे, जिसे कोई बढ़ती हुई टीम बनाए नहीं रख सकती। नेतृत्व के सामने उनकी इकाइयों में मामला बनाएं: एक रोकी गई रेस को टाले गए डेटा-भ्रष्टता घटनाओं में, बैकप्रेशर को रोके गए आउटेज में, और एक सुरक्षित डिफ़ॉल्ट को बचाए गए ऑनबोर्डिंग समय में अनुवादित करें।

एंटी-पैटर्न और नुकसान

  • I/O-बाउंड काम पर गति के लिए थ्रेड जोड़ना। एक प्रतीक्षा-भारी सेवा पर अधिक थ्रेड प्रतिस्पर्धा खरीदते हैं, थ्रूपुट नहीं।
  • हर जगह साझा परिवर्तनशील स्थिति। कोई भी थ्रेड किसी भी ऑब्जेक्ट को बदल दे, तो शुद्धता एक भाग्य का मामला बन जाती है जिसे कोई समीक्षक सत्यापित नहीं कर सकता।
  • असीमित कतारें और पूल। एक उछाल कतार को तब तक बढ़ाता है जब तक मेमोरी नहीं मरती; क्रैश रहस्यमय दिखता है लेकिन साधारण अधिभार है।
  • वैश्विक क्रम के बिना तदर्थ लॉकिंग। कोडबेस में अलग-अलग क्रमों में लिए गए लॉक भार के तहत डेडलॉक करते हैं।
  • यह मान लेना कि कोड लिखे गए क्रम में चलता है। मेमोरी मॉडल की अनदेखी करना, जिससे एक दृश्यता बग एक थ्रेड को एक पुराने मान पर घूमते हुए छोड़ देता है।
  • हाथ से गढ़ी लॉक-फ्री चतुराई। कस्टम लॉक-फ्री योजनाएं लगभग हमेशा सूक्ष्म रूप से गलत होती हैं और समीक्षा करना लगभग असंभव।
  • केवल हैप्पी इंटरलीविंग का परीक्षण। निश्चयात्मक परीक्षण पास हो जाते हैं जबकि दस लाख में एक क्रम उत्पादन को भ्रष्ट कर देता है।
  • लॉक पकड़े हुए अज्ञात कोड कॉल करना। एक कॉलबैक जो ब्लॉक करता है या फिर से प्रवेश करता है, एक क्रिटिकल सेक्शन को डेडलॉक में बदल देता है।

परिपक्वता मॉडल

  • स्तर 1, आरंभ (Initiate): समवर्तीता तदर्थ और प्रतिक्रियात्मक है। थ्रेड और लॉक सहज ज्ञान से जोड़े जाते हैं, साझा परिवर्तनशील स्थिति हर जगह है, और कतारें असीमित हैं। रेस कंडीशन ऐसी पुनरुत्पादन-अयोग्य उत्पादन घटनाओं के रूप में सामने आती हैं जिनका निदान कोई नहीं कर सकता, और उन्हें पकड़ने के लिए कोई टूलिंग मौजूद नहीं है।
  • स्तर 2, विकास (Develop): कुछ टीमों ने बुनियादी प्रथाएं सीख ली हैं: वे अधिक सावधानी से लॉक का उपयोग करती हैं और अपनी सबसे स्पष्ट कतारों को सीमित करती हैं। रेस और डेडलॉक के बारे में अनौपचारिक जागरूकता है, और कुछ महत्वपूर्ण पथों को अतिरिक्त जांच मिलती है। टीमों में प्रथा असंगत है, परीक्षण अभी भी ज़्यादातर एकल-इंटरलीविंग है, और सुरक्षित पैटर्न लेखन में नहीं बल्कि व्यक्तियों में रहते हैं।
  • स्तर 3, मानकीकरण (Standardize): संगठन के पास संगठन-व्यापी लागू एक दस्तावेज़ीकृत घरेलू शैली है: डिफ़ॉल्ट के रूप में अपरिवर्तनीयता और संदेश-पारेषण, कच्चे लॉक की तुलना में उच्च-स्तरीय मॉडल, बैकप्रेशर के साथ सीमित कतारें और पूल, और एक दस्तावेज़ीकृत वैश्विक लॉक क्रम। रेस डिटेक्टर और स्ट्रेस टेस्ट CI में चलते हैं, और समवर्तीता विकल्प इस पर निर्भर करते हैं कि काम I/O-बाउंड है या CPU-बाउंड।
  • स्तर 4, प्रबंधन (Manage): संगठन अपनी समवर्तीता स्थिति को बेसलाइन के विरुद्ध मापता और नियंत्रित करता है। यह सेवाओं में रेस-डिटेक्टर और थ्रेड-सैनिटाइज़र कवरेज को ट्रैक करता है, कतार गहराई, लॉक-प्रतीक्षा समय, पूल संतृप्ति, और अस्वीकृति दरों को निगरानी किए गए मेट्रिक्स के रूप में दर्ज करता है, और गिरावट वक्र का लोड-टेस्ट करता है ताकि हर सीमा एक डेटा-समर्थित क्षमता निर्णय हो। समवर्तीता घटनाओं की गिनती और रुझान किया जाता है, समानांतरित वर्कलोड के क्रमिक भागों को वास्तव में प्राप्त गति-लाभ के विरुद्ध मापा जाता है, और नए डिज़ाइनों पर जाओ या न-जाओ निर्णय सहज ज्ञान के बजाय उस प्रमाण पर टिकते हैं।
  • स्तर 5, ऑर्केस्ट्रेशन (Orchestrate): सुरक्षित समवर्तीता हर लेखक के लिए सबसे कम प्रतिरोध का पथ है, और यह प्रथा संगठन भर में लगातार सुधारी और एकीकृत की जाती है। बग की पूरी श्रेणियां निर्माण द्वारा असंभव हैं, गर्म बिंदुओं को केवल वहां ट्यून किया जाता है जहां प्रोफाइलिंग इसे साबित करे, और निश्चयात्मक रीप्ले दुर्लभ बचे हुए बग को पुनरुत्पादन-योग्य बनाता है। शुद्धता और लेखापरीक्षा-योग्यता लगातार बचाव की गई विशेषताएं हैं, क्षमता सीमाएं देखे गए भार के अनुसार ढलती हैं, और मानक प्लेटफ़ॉर्म तथा वर्कलोड के बदलने के साथ विकसित होते हैं।

चर्चा के लिए विचार

  1. यदि आप आज अपनी सबसे व्यस्त सेवा का ऑडिट करें, तो उसकी कितनी स्थिति साझा और परिवर्तनशील है, और उस साझाकरण में से कितना वास्तव में आवश्यक है?
  2. जब किसी को दो कार्यों को समन्वित करने की आवश्यकता होती है, तो आपकी टीम का डिफ़ॉल्ट उत्तर क्या है, और क्या आप चाहेंगे कि वह अपरिवर्तनीयता हो या संदेश-पारेषण?
  3. आपके सिस्टम में असीमित कतारें या असीमित पूल अभी भी कहां छिपे हैं, और अचानक दस गुना ट्रैफ़िक उछाल के तहत उनका क्या होगा?
  4. क्या आपके निरंतर एकीकरण रन में एक रेस डिटेक्टर या थ्रेड सैनिटाइज़र शामिल है, और आखिरी बार किसी ने उत्पादन से पहले कुछ कब पकड़ा था?
  5. आपके सबसे अधिक समानांतरित वर्कलोड के लिए, क्रमिक भाग क्या है, और क्या एम्डाल का नियम उस गति-लाभ को सीमित करता है जिसका आप वास्तव में पीछा कर रहे हैं?
  6. क्या आपकी टीम मांग पर दस लाख में एक इंटरलीविंग बग को पुनरुत्पादित कर सकती है, और वहां पहुंचने के लिए क्या करना पड़ेगा?

मुख्य निष्कर्ष

  • समवर्तीता एक प्रोग्राम को स्वतंत्र कार्यों के रूप में संरचित करती है; समानांतरता उन्हें एक साथ निष्पादित करती है। थ्रेड जोड़ने से पहले तय करें कि आपको किसकी आवश्यकता है।
  • साझा परिवर्तनशील स्थिति लगभग हर समवर्तीता बग की जड़ है; कई लेखकों के लिए सुरक्षित डिफ़ॉल्ट के रूप में अपरिवर्तनीयता और संदेश-पारेषण को प्राथमिकता दें।
  • हाथ से लिखे लॉक से पहले उच्च-स्तरीय मॉडल (एक्टर, चैनल, संरचित समवर्तीता, async/await) की ओर बढ़ें, जो सिद्धांत में सही और व्यवहार में खतरनाक हैं।
  • परमाणुता, दृश्यता, और अपने मेमोरी मॉडल को समझें; सही प्रिमिटिव का उपयोग करें, लॉक को संक्षिप्त रूप से पकड़ें, और डेडलॉक, लाइवलॉक, तथा भुखमरी से बचने के लिए एक वैश्विक लॉक क्रम लागू करें।
  • हर कतार और पूल की सीमा तय करें और बैकप्रेशर लागू करें, ताकि एक उछाल क्रैश होने के बजाय शालीनता से गिरावट करे (अध्याय 3.3)।
  • रेस डिटेक्टर, स्ट्रेस टेस्ट, और रीप्ले (अध्याय 2.15) के साथ जानबूझकर इंटरलीविंग का परीक्षण करें, और समानांतरीकरण करते समय एम्डाल के नियम (अध्याय 2.16) का सम्मान करें।
  • उद्यमों के लिए यह थ्रूपुट और रोकी गई घटनाएं हैं; सरकार के लिए यह दीर्घकालिक सिस्टमों में शुद्धता और लेखापरीक्षा-योग्यता है।

संदर्भ और आगे का अध्ययन

  • Brian Goetz et al., Java Concurrency in Practice (atomicity, visibility, the memory model, and safe publication).
  • Herb Sutter, “The Free Lunch Is Over” (why software must embrace concurrency as clock speeds plateau).
  • Leslie Lamport, “Time, Clocks, and the Ordering of Events in a Distributed System” (ordering and the foundations of concurrent reasoning).
  • C. A. R. Hoare, “Communicating Sequential Processes” (Communications of the ACM, 1978): the CSP model behind channels.
  • Carl Hewitt, Peter Bishop, and Richard Steiger, “A Universal Modular Actor Formalism for Artificial Intelligence” (the origin of the actor model).
  • Edsger W. Dijkstra, “Cooperating Sequential Processes” (semaphores, mutual exclusion, and the deadlock problem).
  • Maurice Herlihy and Nir Shavit, The Art of Multiprocessor Programming (locks, atomics, and lock-free data structures).
  • Nathaniel J. Smith, “Notes on Structured Concurrency, or: Go Statement Considered Harmful” (the case for structured concurrency).
  • Martin Kleppmann, Designing Data-Intensive Applications (concurrency and consistency where memory meets distributed systems).
  • Gene M. Amdahl, “Validity of the Single Processor Approach to Achieving Large-Scale Computing Capabilities” (1967): the origin of Amdahl’s law.