Android 3.0 और इसके बाद के प्लैटफ़ॉर्म वर्शन, Chrome OS के साथ काम करने के लिए ऑप्टिमाइज़ किए गए हैं मल्टीप्रोसेसर आर्किटेक्चर शामिल हैं. इस दस्तावेज़ में ऐसी समस्याओं के बारे में बताया गया है जो C, C++, और Java में सिमेट्रिक मल्टीप्रोसेसर सिस्टम के लिए मल्टीथ्रेड कोड लिखते समय यह आ सकता है (इसके बाद, इसे सिर्फ़ “Java” कहा जाता है. कम शब्दों में दी जाने वाली जानकारी. इसे Android ऐप्लिकेशन डेवलपर के लिए, शुरुआती जानकारी देने के मकसद से बनाया गया है चर्चा करें.
परिचय
एसएमपी, “सिमेट्रिक मल्टी-प्रोसेसर” का छोटा नाम है. यह ऐसी डिज़ाइन के बारे में बताता है जिसमें कौनसे दो या उससे ज़्यादा एक जैसे सीपीयू कोर, मुख्य मेमोरी का ऐक्सेस शेयर करते हैं. पूरा होने तक कुछ साल पहले, सभी Android डिवाइस UP (Uni-प्रोसेसर) थे.
ज़्यादातर — अगर सभी नहीं — Android डिवाइसों में हमेशा एक से ज़्यादा सीपीयू होते थे, लेकिन पहले उनमें से सिर्फ़ एक का इस्तेमाल ऐप्लिकेशन चलाने के लिए किया जाता था, जबकि दूसरे ऐप्लिकेशन के अलग-अलग बिट को मैनेज करते थे हार्डवेयर (उदाहरण के लिए, रेडियो). सीपीयू के अलग-अलग आर्किटेक्चर हो सकते हैं और प्रोग्राम, हर प्रोग्राम के साथ कम्यूनिकेट करने के लिए, मुख्य मेमोरी का इस्तेमाल नहीं कर पाते अन्य.
आज-कल बिकने वाले ज़्यादातर Android डिवाइस, एसएमपी डिज़ाइन के हिसाब से बनाए जाते हैं, इससे सॉफ़्टवेयर डेवलपर के लिए चीज़ें बनाना थोड़ा और मुश्किल हो जाता है. रेस की शर्तें हो सकता है कि मल्टी-थ्रेड वाले प्रोग्राम में यूनिप्रोसेसर पर दिखाई देने वाली समस्याएं न हों, हालांकि, हो सकता है कि आपकी दो या उससे ज़्यादा थ्रेड में, समय-समय पर गड़बड़ी हो जाए एक साथ अलग-अलग कोर पर चल रहे हों. इसके अलावा, अलग-अलग डिवाइसों पर कोड चलाने पर, कोड के क्रैश होने की आशंका कम या ज़्यादा हो सकती है प्रोसेसर आर्किटेक्चर या उसी टेक्नोलॉजी के अलग-अलग इंप्लीमेंटेशन पर आर्किटेक्चर. x86 पर अच्छी तरह से टेस्ट किया गया कोड, ARM पर खराब हो सकता है. ज़्यादा मॉडर्न कंपाइलर के साथ फिर से कंपाइल करने पर, हो सकता है कि कोड काम न करे.
इस दस्तावेज़ के बाकी हिस्से में इसकी वजह बताई गई है. साथ ही, यह भी बताया गया है कि आगे आपको क्या करना होगा ताकि यह पक्का किया जा सके कि आपका कोड सही तरीके से काम कर रहा है.
मेमोरी के कंसिस्टेंसी मॉडल: एसएमपी अलग क्यों होते हैं
यह एक मुश्किल विषय की तेज़ी से और पूरी जानकारी देने के लिए है. कुछ चीज़ों में अधूरा न हो, लेकिन कोई भी जानकारी गुमराह करने वाली या गलत न हो. अगर आपने अगले सेक्शन में दिखेगा. आम तौर पर, यहां दी गई जानकारी अहम नहीं होती.
इसके लिए दस्तावेज़ के अंत में आगे पढ़ें की तरफ़ इशारा करता है.
मेमोरी के एक जैसे होने वाले मॉडल या अक्सर सिर्फ़ “मेमोरी मॉडल” में, प्रोग्रामिंग भाषा या हार्डवेयर आर्किटेक्चर की गारंटी देता है मेमोरी के ऐक्सेस के बारे में बताती है. उदाहरण के लिए, अगर आप A को पता करने के लिए कोई मान लिखते हैं और फिर B को पते देने के लिए एक मान लिखते हैं, तो मॉडल इस बात की गारंटी दे सकता है कि हर सीपीयू कोर, ऑर्डर.
ज़्यादातर प्रोग्रामर जिस मॉडल के आदी हैं वह क्रमिक नियमितता, जिसकी जानकारी इस तरह दी गई है (Adve & घाराचोर्लू):
- ऐसा लगता है कि मेमोरी से जुड़ी सभी कार्रवाइयां एक-एक करके करती हैं
- एक थ्रेड में सभी कार्रवाइयां बताए गए क्रम में लागू होती हैं प्रोसेसर के प्रोग्राम से हो सकता है.
कुछ समय के लिए मान लीजिए कि हमारे पास एक बहुत ही आसान कंपाइलर या इंटरप्रेटर है जो हैरान करने वाली बात नहीं है: इसका अनुवाद दिए गए हैं, ताकि निर्देशों को ठीक के हिसाब से, हर ऐक्सेस के लिए एक निर्देश. हम यह भी मानेंगे कि वह आसानी से काम करती है जिसे हर थ्रेड अपने प्रोसेसर पर एक्ज़ीक्यूट करता है.
अगर किसी कोड को देखने पर, आपको पता चलता है कि वह कोड पढ़ने और लिखने की सुविधा देता है है, जो क्रमिक रूप से संगत CPU संरचना पर होती हैं, तो आपको पता है कि वे पढ़ें और लिखेंगे. ऐसा हो सकता है कि सीपीयू असल में, निर्देशों का क्रम बदल रहा है और पढ़ने और लिखने में देरी कर रहा है, लेकिन असल में डिवाइस पर चलने वाले कोड के लिए यह बताने का कोई तरीका नहीं है कि सीपीयू कुछ कर रहा है उन पर आसान तरीके से कोई कार्रवाई नहीं की जा सकती. (हम मेमोरी-मैप किया गया डिवाइस ड्राइवर I/O.)
इन बातों को समझाने के लिए, कोड के छोटे स्निपेट जोड़ना, इन्हें आम तौर पर लिटमस टेस्ट कहा जाता है.
यहां एक आसान उदाहरण दिया गया है, जिसमें कोड दो थ्रेड पर चल रहा है:
थ्रेड 1 | थ्रेड 2 |
---|---|
A = 3 |
reg0 = B |
इस और भविष्य के सभी लिटमस उदाहरणों में, मेमोरी की जगहों को इस तरह दिखाया गया है कैपिटल लेटर (A, B, C) और सीपीयू रजिस्टर, “reg” से शुरू होते हैं. सभी मेमोरी शुरुआत में शून्य मिलता है. निर्देश ऊपर से नीचे तक लागू किए जाते हैं. यहां, थ्रेड 1 वैल्यू 3 को जगह A पर सेव करता है. इसके बाद, वैल्यू 5 को जगह B पर सेव करता है. थ्रेड 2 वैल्यू को लोकेशन B से reg0 में लोड करता है. इसके बाद, जगह A को reg1 में जोड़ें. (ध्यान दें कि हम एक क्रम में लिख रहे हैं और अन्य.)
थ्रेड 1 और थ्रेड 2 को अलग-अलग सीपीयू कोर पर एक्ज़ीक्यूट किया जाता है. आपने लोगों तक पहुंचाया मुफ़्त में क्या आपके बारे में सोचते समय हमेशा यह अनुमान लगाना चाहिए मल्टी-थ्रेड वाला कोड.
क्रम से एक जैसे होने से इस बात की गारंटी मिलती है कि दोनों थ्रेड के खत्म होने के बाद लागू होने पर, रजिस्टर इनमें से किसी एक स्थिति में होंगे:
रजिस्टर करें | राज्य |
---|---|
reg0=5, reg1=3 | संभव है (थ्रेड 1 पहले चलाया गया) |
reg0=0, reg1=0 | संभव है (पहले थ्रेड 2 चलाया गया) |
reg0=0, reg1=3 | संभव है (एक साथ चल रहा एक्ज़ीक्यूशन) |
reg0=5, reg1=0 | कभी नहीं |
ऐसी स्थिति में जाने के लिए जहां स्टोर A को देखने से पहले हमें B=5 दिखे, या तो ऐसा होना चाहिए कि इसे पढ़ा न गया हो या लिखा गया हो. ऐसा नहीं हो सकता.
x86 और ARM समेत यूनी-प्रोसेसर, आम तौर पर एक जैसा काम करते हैं. जब ओएस कर्नेल स्विच होता है, तब थ्रेड इंटरलीव किए गए तरीके से एक्ज़ीक्यूट होते हैं ट्रैक कर सकते हैं. ज़्यादातर एसएमपी सिस्टम, जिनमें x86 और ARM शामिल हैं. क्रम में एक जैसा नहीं होता. उदाहरण के लिए, यह हार्डवेयर मेमोरी तक पहुंचने के दौरान उसे बफ़र स्टोर में रखता है, ताकि वे इससे मेमोरी तुरंत ऐक्सेस नहीं हो पाती और अन्य कोर दिखने लगते हैं.
बारीकियां काफ़ी हद तक अलग-अलग हो सकती हैं. उदाहरण के लिए, x86, हालांकि क्रम के मुताबिक नहीं एक जैसा है, फिर भी गारंटी देता है कि reg0 = 5 और reg1 = 0 नामुमकिन है. स्टोर बफ़र किए जाते हैं, लेकिन उनका ऑर्डर बना रहता है. वहीं, ARM में इस तरह का कोई विकल्प नहीं होता. बफ़र किए गए स्टोर का क्रम यह नहीं है साथ ही, हो सकता है कि स्टोर एक ही समय में दूसरे सभी कोर तक न पहुंच पाएं. ये अंतर प्रोग्रामर को तैयार करने के लिए अहम हैं. हालांकि, जैसा कि हम नीचे देखेंगे, C, C++, या Java प्रोग्रामर और इसे इस तरह से प्रोग्राम करना चाहिए कि इस तरह के आर्किटेक्चर के अंतर छिप जाएं.
अब तक, हमने यह माना है कि यह सिर्फ़ हार्डवेयर निर्देशों को फिर से क्रम में लगाता है. असल में, कंपाइलर निर्देश को फिर से क्रम में लगाकर परफ़ॉर्मेंस बेहतर बनाने के लिए किया जा सकता है. हमारे उदाहरण में, कंपाइलर यह तय कर सकता है कि कुछ और थ्रेड 2 के कोड में reg0 की ज़रूरत होने से पहले, reg1 की वैल्यू होना ज़रूरी है. इसलिए, यह कोड लोड हो रहा है सबसे पहले reg1. इसके अलावा, ऐसा भी हो सकता है कि पहले से कुछ कोड ने पहले ही A और कंपाइलर को लोड कर दिया हो A को फिर से लोड करने के बजाय उस वैल्यू का दोबारा इस्तेमाल करने का फ़ैसला ले सकता है. दोनों ही मामलों में, reg0 और reg1 के लोड का क्रम बदला जा सकता है.
अलग-अलग मेमोरी की जगहों के ऐक्सेस फिर से क्रम में लगाना, हार्डवेयर या कंपाइलर में, अनुमति है, क्योंकि इससे किसी थ्रेड के एक्ज़ीक्यूशन पर असर नहीं पड़ता और इससे परफ़ॉर्मेंस में काफ़ी सुधार हो सकता है. जैसा कि हम देखेंगे कि थोड़ी सी सावधानी से, हम इसे मल्टीथ्रेड प्रोग्राम के नतीजों पर असर डालने से भी रोक सकते हैं.
कंपाइलर भी मेमोरी ऐक्सेस को फिर से क्रम में लगा सकते हैं. इसलिए, असल में यह समस्या एसएमपी के लिए नया नहीं है. यूनिप्रोसेसर पर भी, कंपाइलर हमारे उदाहरण में reg0 और reg1 हैं और थ्रेड 1 को क्रम में लगाए गए निर्देश. हालांकि, अगर हमारा कंपाइलर फिर से ऑर्डर नहीं करता है, तो हम कभी भी इस समस्या का आकलन नहीं करना चाहिए. ज़्यादातर ARM एसएमपी पर, कंपाइलर के बिना भी फिर से ऑर्डर करते समय, शायद यह देखा जाए कि सफल निष्पादनों की संख्या. जब तक कि आप असेंबली में प्रोग्रामिंग न कर रहे हों भाषा, एसएमपी आम तौर पर इस बात की संभावना को और बढ़ा देते हैं कि आपको वे समस्याएं दिखाई दें जो साथ-साथ.
डेटा-रेस-फ़्री प्रोग्रामिंग
अच्छी बात यह है कि इनमें से किसी भी चीज़ के बारे में सोचने से बचने का एक आसान तरीका है ये विवरण. अगर आप कुछ आसान नियमों का पालन करते हैं, तो आम तौर पर ऐसा करना सुरक्षित होता है "क्रम से लगाए गए एक जैसे फ़ंक्शन" को छोड़कर, पिछले सभी सेक्शन को भूल जाने के लिए . माफ़ करें, अन्य Android विजेट आपके लिए उपलब्ध हो सकते हैं, अगर गलती से उन नियमों का उल्लंघन नहीं होता.
मॉडर्न प्रोग्रामिंग लैंग्वेज, "डेटा-रेस-फ़्री" कैटगरी को बढ़ावा देती हैं प्रोग्रामिंग स्टाइल. जब तक यह वादा न किया जाए कि "डेटा के रेस" लागू नहीं होंगे, कुछ ऐसे कंस्ट्रक्ट से बचें जो कंपाइलर को अलग तरीके से बताते हैं, कंपाइलर और हार्डवेयर, लगातार एक जैसे नतीजे देने का वादा करता है. ऐसा नहीं होता है इसका मतलब है कि वे मेमोरी ऐक्सेस को फिर से क्रम में लगाने से बचते हैं. इसका मतलब यह है कि अगर उन नियमों का पालन करें जो आपको यह नहीं बता पाएंगे कि मेमोरी ऐक्सेस फिर से क्रम में लगाया गया. यह ठीक वैसे ही है जैसे आपको यह बताना होता है कि सॉसेज बहुत स्वादिष्ट है और भूख लगने वाला खाना, जब तक कि आप यहां नहीं जाने का वादा करते हैं सॉसेज फ़ैक्ट्री. डेटा की रेस ही मेमोरी के बारे में भद्दी सच को सामने लाती हैं फिर से ऑर्डर करना.
"डेटा रेस" क्या है?
डेटा रेस तब होती है, जब कम से कम दो थ्रेड एक साथ ऐक्सेस करते हैं एक समान साधारण डेटा होता है और उनमें से कम से कम एक उसे संशोधित करता है. "सामान्य डेटा" हमारा मतलब कुछ ऐसा है जो खास तौर पर कोई सिंक्रोनाइज़ेशन ऑब्जेक्ट नहीं है का इस्तेमाल थ्रेड कम्यूनिकेशन के लिए किया जाता है. म्यूटेक्स, कंडिशन वैरिएबल, Java वोलाटील या C++ ऐटॉमिक ऑब्जेक्ट कोई सामान्य डेटा नहीं हैं और उनका ऐक्सेस रेस में हिस्सा लेने की अनुमति है. असल में, उनका इस्तेमाल किसी दूसरे नेटवर्क पर डेटा रेस को रोकने के लिए किया जाता है ऑब्जेक्ट हैं.
यह पता करने के लिए कि दो थ्रेड एक साथ एक ही को ऐक्सेस करते हैं या नहीं
मेमोरी की जगह की जानकारी है, तो हम ऊपर बताई गई मेमोरी को फिर से क्रम में लगाने वाली चर्चा को अनदेखा कर सकते हैं और
क्रम में एक जैसा होने का एहसास दिलाता है. इस प्रोग्राम में डेटा खर्च करने की कोई ज़रूरत नहीं है
अगर A
और B
साधारण बूलियन वैरिएबल हैं, जो
शुरुआत में गलत:
थ्रेड 1 | थ्रेड 2 |
---|---|
if (A) B = true |
if (B) A = true |
कार्रवाइयों का क्रम नहीं बदला जाता है. इसलिए, दोनों शर्तों का मान 'गलत' होगा, और
दोनों में से कोई भी वैरिएबल कभी अपडेट नहीं किया जाता. इसलिए, डेटा की रेस नहीं हो सकती. यहां है
आपको इस बारे में चिंता करने की ज़रूरत नहीं है कि A
से लोड होने पर क्या होगा
और B
में स्टोर करें
थ्रेड 1 का क्रम किसी तरह बदल दिया गया था. कंपाइलर को Thread का क्रम बदलने की अनुमति नहीं है
1. इसके लिए, मैसेज को "B = true; if (!A) B = false
" के तौर पर फिर से लिखें. यह काफ़ी होगा
जैसे, दिन की रोशनी में शहर के बीच में सॉसेज बनाना.
डेटा रेस को आधिकारिक तौर पर, बेसिक बिल्ट-इन टाइप के आधार पर तय किया जाता है. जैसे, पूर्णांक और
रेफ़रंस या पॉइंटर. किसी int
को एक साथ असाइन करना
इसे किसी दूसरे थ्रेड में पढ़ना, साफ़ तौर पर डेटा की दौड़ है. हालांकि, C++
स्टैंडर्ड लाइब्रेरी और
लाइब्रेरी को इस तरह लिखा गया है कि आप अलग-अलग वजहों से
में अलग-अलग डेटा इकट्ठा करने की सुविधा मिलती है. वे वादा करते हैं कि डेटा रेस की शुरुआत नहीं की जाएगी
जब तक एक ही कंटेनर में समवर्ती एक्सेस न हो, कम से कम एक
जो इसे अपडेट करता है. इस समय एक थ्रेड में set<T>
अपडेट किया जा रहा है
साथ ही किसी दूसरे पेज में पढ़ने से लाइब्रेरी को
डेटा रेस होती है और इसलिए अनौपचारिक तौर पर इसे "लाइब्रेरी लेवल की डेटा रेस" माना जा सकता है.
ठीक इसके उलट, पढ़ने के दौरान एक थ्रेड में एक set<T>
अपडेट करना
एक-दूसरे से अलग है, जिसकी वजह से डेटा रेस नहीं होती, क्योंकि
लाइब्रेरी वादा करती है कि इस मामले में (निचले लेवल) डेटा रेस शुरू नहीं होगी.
आम तौर पर, किसी डेटा स्ट्रक्चर में अलग-अलग फ़ील्ड को एक साथ ऐक्सेस किया जाता है डेटा रेस शुरू नहीं की जा सकती. हालांकि, विज्ञापन देने वालों के लिए, यह नियम: C या C++ में बिट-फ़ील्ड के निरंतर क्रम को एकल "मेमोरी स्थान". ऐसे क्रम में किसी भी बिट-फ़ील्ड को ऐक्सेस करना को उन सभी को ऐक्सेस करना माना जाता है जिनसे यह तय होता है कि डेटा रेस की मौजूदगी. यह कॉमन हार्डवेयर की क्षमता से जुड़ी गड़बड़ी को दिखाता है का इस्तेमाल करें. Java प्रोग्रामर में इससे मिलती-जुलती कोई समस्या नहीं होती.
डेटा के उतार-चढ़ाव से बचना
मॉडर्न प्रोग्रामिंग भाषाएं कई तरह से सिंक करती हैं के तरीके इस्तेमाल करते हैं. सबसे बुनियादी टूल ये हैं:
- लॉक या म्यूटेक्स
- म्यूटेक्स (C++11
std::mutex
, याpthread_mutex_t
) या Java में मौजूदsynchronized
ब्लॉक का इस्तेमाल यह पक्का करने के लिए किया जा सकता है कि कुछ कोड का सेक्शन, कोड ऐक्सेस करने के अन्य सेक्शन के साथ एक साथ नहीं चलता एक ही डेटा है. हम इन और इससे मिलती-जुलती दूसरी सुविधाओं को सामान्य रूप से देखेंगे जैसे कि "लॉक". शेयर किए गए डेटा को ऐक्सेस करने से पहले, लगातार एक खास लॉक की सुविधा डेटा स्ट्रक्चर और उसे बाद में रिलीज़ करने से, ऐक्सेस करते समय डेटा के रेस से बचने में मदद मिलती है इस्तेमाल किया जा सकता है. इससे यह भी पक्का होता है कि ऐप्लिकेशन को अपडेट और ऐक्सेस करना थोड़ा ज़रूरी है. इसका मतलब है कि डेटा स्ट्रक्चर के अन्य अपडेट को बीच में चलाया जा सकता है. यह तो योग्य है यह अब तक का सबसे ज़्यादा इस्तेमाल किया जाने वाला टूल है. Java का इस्तेमालsynchronized
ब्लॉक या C++lock_guard
याunique_lock
पक्का कर सकता है कि लॉक सही तरीके से किसी अपवाद की घटना हो सकती है. - वोलाटाइल/ऐटॉमिक वैरिएबल
- Java
volatile
फ़ील्ड देता है जो एक साथ ऐक्सेस करने की सुविधा देता है वह भी कई तरह के डेटा की जांच किए बिना. 2011 से, C और C++ का इस्तेमाल किया जा रहा है एक जैसे सिमैंटिक वालेatomic
वैरिएबल और फ़ील्ड. ये हैं लॉक के मुकाबले, आम तौर पर इस्तेमाल करना ज़्यादा मुश्किल होता है, क्योंकि इनसे सिर्फ़ यह पक्का होता है कि किसी एक वैरिएबल का अलग-अलग ऐक्सेस, ऐटॉमिक होता है. (C++ में यह सामान्य तौर पर होता है पढ़ने-लिखने में बदलाव करने में मददगार, जैसे कि इंक्रीमेंट. जावा उसके लिए विशेष विधि कॉल की आवश्यकता होती है.) लॉक के उलट,volatile
याatomic
वैरिएबल का इस्तेमाल नहीं किया जा सकता इसका इस्तेमाल सीधे तौर पर किया जाना चाहिए, ताकि दूसरे थ्रेड लंबे कोड क्रमों में रुकावट न डालें.
यह ध्यान रखना ज़रूरी है कि volatile
का मतलब C++ और Java में है. C++ में, volatile
डेटा को ऐक्सेस करने से नहीं रोकता
होता है, जबकि पुराना कोड अक्सर इसका इस्तेमाल
atomic
ऑब्जेक्ट. अब इसका सुझाव नहीं दिया जाता; इंच
C++, atomic<T>
का इस्तेमाल उन वैरिएबल के लिए करें जो एक साथ हो सकते हैं
कई थ्रेड ने ऐक्सेस किया हो. C++ volatile
का मतलब है
डिवाइस रजिस्टर और इसी तरह के अन्य काम कर सकता है.
C/C++ atomic
वैरिएबल या Java volatile
वैरिएबल
का इस्तेमाल अन्य वैरिएबल पर डेटा रेस को रोकने के लिए किया जा सकता है. अगर flag
है
atomic<bool>
प्रकार होने का एलान किया गया
या atomic_bool
(C/C++) या volatile boolean
(Java),
और शुरुआत में गलत है, तो नीचे दिया गया स्निपेट डेटा-रेस-फ़्री है:
थ्रेड 1 | थ्रेड 2 |
---|---|
A = ...
|
while (!flag) {}
|
थ्रेड 2, flag
के सेट होने का इंतज़ार कर रही है. इसलिए,
थ्रेड 2 में A
,
थ्रेड 1 में A
को असाइनमेंट असाइन किया गया. इसलिए, इन पर डेटा इकट्ठा होने की कोई संभावना नहीं है
A
. flag
को हुई दौड़ को डेटा की दौड़ के तौर पर नहीं गिना जाता,
क्योंकि, बार-बार अपडेट होने वाले डेटा या ऐटॉमिक ऐक्सेस, "सामान्य मेमोरी ऐक्सेस" नहीं होते.
मेमोरी को फिर से क्रम में लगाने से रोकने या छिपाने के लिए, लागू करना ज़रूरी है यह पिछले लिटमस टेस्ट जैसा कोड बनाने के लिए सही तरीके से काम करता है. इससे आम तौर पर, डेटा बार-बार अपडेट करने वाले या ऐटॉमिक स्टोरेज का ऐक्सेस मिलता है सामान्य ऐक्सेस से ज़्यादा महंगा है.
हालांकि पहले दिया गया उदाहरण डेटा-रेस-फ़्री है, लेकिन यह एक साथ लॉक होता है
Java में Object.wait()
या आम तौर पर C/C++ में कंडिशन वैरिएबल
आपके लिए एक बेहतर समाधान उपलब्ध कराना होगा. इसमें बार-बार इंतज़ार करने की ज़रूरत नहीं होती
बैटरी तेज़ी से खर्च हो रही है.
मेमोरी का क्रम बदलने की सूचना दिखने पर
डेटा-रेस-फ़्री प्रोग्रामिंग आम तौर पर, हमें अलग-अलग समय पर इसमें मेमोरी के ऐक्सेस को फिर से क्रम में लगाने की समस्याएं होती हैं. हालांकि, ऐसे कई मामले हैं जिनमें इससे क्रम में लगाने की प्रोसेस दिखने लगती है:- अगर आपके प्रोग्राम में कोई गड़बड़ी है, जिसकी वजह से अनजाने में डेटा इकट्ठा हो रहा है,
कंपाइलर और हार्डवेयर ट्रांसफ़ॉर्मेशन ऐक्शन को देख सकते हैं. साथ ही,
आपका प्रोग्राम हैरान हो सकता है. उदाहरण के लिए, अगर हम
पिछले उदाहरण में,
flag
में बार-बार बदलाव होने पर, थ्रेड 2 मेंA
को शुरू नहीं किया गया. या कंपाइलर यह तय कर सकता है कि फ़्लैग Thread 2 के लूप में बदला जा सकता है और प्रोग्राम कोथ्रेड 1 थ्रेड 2 A = ...
flag = truereg0 = फ़्लैग; जबकि (!reg0) {}
... = Aflag
सही है. - C++ में सुकून और शांति देने वाली सुविधाएं मिलती हैं
किसी नतीजे का क्रम में सटीक मिलान न होने के बावजूद, भले ही कोई नस्ल न हो. ऐटमिक ऑपरेशन
स् पष्ट
memory_order_
... तर्क ले सकता है. इसी तरह,java.util.concurrent.atomic
पैकेज ज़्यादा पाबंदी वाला पैकेज है एक जैसी सुविधाओं का सेट. इनमें खास तौर परlazySet()
शामिल है. और जावा प्रोग्रामर कभी-कभी एक जैसे असर के लिए, जान-बूझकर डेटा इकट्ठा करने वाली रेस का इस्तेमाल करते हैं. इन सभी तरीकों से, कैंपेन की परफ़ॉर्मेंस बेहतर होती है लागत जटिल होती है. हम उनके बारे में सिर्फ़ कुछ देर के लिए चर्चा करते हैं नीचे देखें. - कुछ C और C++ कोड किसी पुरानी स्टाइल में लिखे गए हैं, न कि पूरी तरह से
मौजूदा भाषा मानकों के साथ काम करते हैं, जिसमें
volatile
atomic
के बजाय वैरिएबल का इस्तेमाल किया जाता है और मेमोरी को क्रम में लगाया जाता है फ़ेंस डालने की अनुमति नहीं है या बाधाओं को कम करने में मदद मिलती है. इसमें ऐक्सेस के बारे में साफ़ तौर पर तर्क देना ज़रूरी है हार्डवेयर मेमोरी मॉडल को फिर से क्रम में लगाने और समझने में मदद करता है. कोडिंग की स्टाइल के साथ इन पंक्तियों का अभी भी Linux कर्नेल में उपयोग किया जाता है. इसे ऐसा नहीं करना चाहिए को नए Android ऐप्लिकेशन में इस्तेमाल किया जा सकता है और इसके बारे में यहां और चर्चा नहीं की गई है.
प्रैक्टिस करें
मेमोरी के एक जैसे होने से जुड़ी समस्याओं को डीबग करना बहुत मुश्किल हो सकता है. अगर कोई
लॉक, atomic
या volatile
एलान की वजह
पुराना डेटा पढ़ने के लिए कुछ कोड का इस्तेमाल करते हैं, तो शायद आप ये काम न कर पाएं
डीबगर की मदद से मेमोरी डंप की जांच करके इसकी वजह जानें. जितना हो सके, उतना समय
डीबगर क्वेरी जारी करते हैं, तो CPU कोर शायद
साथ ही, मेमोरी का कॉन्टेंट और सीपीयू रजिस्टर
“नामुमकिन” स्थिति.
C में क्या नहीं करें
यहां हम गलत कोड के कुछ उदाहरण तथा ऐसे आसान तरीके बताते हैं समस्या हल करें. ऐसा करने से पहले, हमें बुनियादी भाषा के इस्तेमाल के बारे में चर्चा करनी होगी सुविधा.
C/C++ और "वोलेटाइल"
C और C++ volatile
एलान बहुत खास मकसद वाले टूल हैं.
वे कंपाइलर को वोलेटाइल को फिर से क्रम में लगाने या हटाने से रोकते हैं
ऐक्सेस करता है. यह हार्डवेयर डिवाइस रजिस्टर को ऐक्सेस करने वाले कोड के लिए मददगार हो सकता है,
मेमोरी एक से ज़्यादा स्थान पर या के साथ मैप की गई हो
setjmp
. हालांकि, Java के उलट, C और C++ volatile
volatile
को थ्रेड कम्यूनिकेशन के लिए नहीं बनाया गया है.
C और C++ में, volatile
का ऐक्सेस है
डेटा को फिर से क्रम में लगाकर, उसे नॉन-वोलाटाइल डेटा पर ऐक्सेस किया जा सकता है. साथ ही,
ऊर्जा की गारंटी देता है. इसलिए, इनके बीच डेटा शेयर करने के लिए volatile
का इस्तेमाल नहीं किया जा सकता
यूनिप्रोसेसर पर भी, पोर्टेबल कोड में थ्रेड होते हैं. आम तौर पर, C volatile
में यह सुविधा काम नहीं करती
यह हार्डवेयर के ज़रिए ऐक्सेस को फिर से क्रम में लगाने से रोकता है. इसलिए, यह अपने-आप में और भी कम उपयोगी है
कई थ्रेड वाले SMP एनवायरमेंट. यही वजह है कि C11 और C++11 फ़ॉर्मैट काम करते हैं
atomic
ऑब्जेक्ट. इसके बजाय, आपको उनका इस्तेमाल करना चाहिए.
कई पुराने C और C++ कोड, अब भी थ्रेड के लिए volatile
का गलत इस्तेमाल करते हैं
बातचीत करते हैं. यह अक्सर उस डेटा के लिए सही तरीके से काम करता है जिसे
में इस्तेमाल किया जाता है, बशर्ते उसका इस्तेमाल साफ़ तौर पर बाड़ लगाने के साथ या किसी मामले में किया गया हो
जिसमें मेमोरी का क्रम ज़रूरी नहीं है. हालांकि, इसके काम करने की कोई गारंटी नहीं है
सही तरीके से कॉन्फ़िगर करें.
उदाहरण
ज़्यादातर मामलों में, लॉक लगा देना बेहतर होगा (जैसे,
pthread_mutex_t
या C++11 std::mutex
) शामिल करें, न कि
लेकिन हम बाद वाली कार्रवाई का इस्तेमाल करके यह समझाएंगे कि वे कैसे
जिनका इस्तेमाल व्यावहारिक तौर पर किया जाता है.
MyThing* gGlobalThing = NULL; // Wrong! See below. void initGlobalThing() // runs in Thread 1 { MyStruct* thing = malloc(sizeof(*thing)); memset(thing, 0, sizeof(*thing)); thing->x = 5; thing->y = 10; /* initialization complete, publish */ gGlobalThing = thing; } void useGlobalThing() // runs in Thread 2 { if (gGlobalThing != NULL) { int i = gGlobalThing->x; // could be 5, 0, or uninitialized data ... } }
मकसद यह है कि हम एक स्ट्रक्चर तय करें, उसके फ़ील्ड शुरू करें और आखिर में, हम उसे ग्लोबल वैरिएबल में स्टोर करके "पब्लिश" करते हैं. उस समय, किसी दूसरे थ्रेड में इसे देखा जा सकता है. हालांकि, यह ठीक है, क्योंकि यह पूरी तरह से शुरू हो गया है, है न?
समस्या यह है कि gGlobalThing
के स्टोर का पता लग सकता है
होने से पहले फ़ील्ड शुरू कर देते हैं, आम तौर पर इसकी वजह यह है कि कंपाइलर या
प्रोसेसर ने स्टोर को gGlobalThing
में फिर से क्रम में लगा दिया और
thing->x
. thing->x
से पढ़ने वाले किसी दूसरे थ्रेड को यह समझा जा सका
5, 0 या शुरू न किया गया डेटा भी देख सकते हैं.
यहां मुख्य समस्या, gGlobalThing
पर डेटा इकट्ठा करने की प्रोसेस के बारे में है.
अगर थ्रेड 1, थ्रेड 2 के दौरान initGlobalThing()
को कॉल करती है
useGlobalThing()
, gGlobalThing
को कॉल किए जा सकते हैं
लिखे जाने के दौरान ही पढ़ा जा सकता है.
इस समस्या को ठीक करने के लिए, gGlobalThing
को यह एलान करें:
ऐटमिक. C++11 में:
atomic<MyThing*> gGlobalThing(NULL);
इससे यह पक्का होता है कि लिखे गए शब्द, दूसरे थ्रेड में भी देखे जा सकेंगे
सही क्रम में. इससे कुछ अन्य गड़बड़ियों को रोकने की गारंटी भी मिलती है
ऐसे मोड जिनका इस्तेमाल करने की अनुमति फिर भी दी गई हो, लेकिन असल में इनके होने की संभावना कम हो
Android हार्डवेयर. उदाहरण के लिए, इससे पक्का होता है कि हम
gGlobalThing
पॉइंटर, जिसमें सिर्फ़ कुछ ही लिखा गया है.
Java में क्या नहीं करना चाहिए
हमने Java की कुछ ज़रूरी सुविधाओं के बारे में नहीं बताया है. इसलिए, हम पहले उन पर नज़र डालें.
तकनीकी रूप से Java को, डेटा-रेस-फ़्री होने के लिए कोड की ज़रूरत नहीं है. और यह रहा बहुत सावधानी से लिखे गए Java कोड का एक छोटा सा हिस्सा है, जो सही तरीके से काम करता है में मदद मिलती है. हालांकि, ऐसा कोड लिखना बहुत मुश्किल होता है पेचीदा. हम इसके बारे में नीचे कम शब्दों में बताते हैं. मामले बनाने में और भी खराब, ऐसे कोड का अर्थ बताने वाले विशेषज्ञ अब ब्यौरा सही है. (डेटा-रेस-फ़्री के लिए स्पेसिफ़िकेशन ठीक है कोड.)
फ़िलहाल, हम डेटा-रेस-फ़्री मॉडल का पालन करेंगे, जिसके लिए Java
गारंटी एक जैसी है, जो C और C++ जैसी ही है. एक बार फिर, यह भाषा हमें
कुछ प्रिमिटिव हैं जो खास तौर पर क्रम में लगने वाले कंसिस्टेंसी को कम करते हैं,
lazySet()
और weakCompareAndSet()
कॉल
java.util.concurrent.atomic
में.
C और C++ की तरह ही, फ़िलहाल हम इन पर ध्यान नहीं देंगे.
Java का "सिंक किया गया" और "अनियमित" कीवर्ड
“सिंक किया गया” कीवर्ड, Java लैंग्वेज की पहले से मौजूद लॉकिंग सुविधा देता है मैकेनिज़्म. हर ऑब्जेक्ट का एक “मॉनिटर” होता है. इसका इस्तेमाल इन चीज़ों के लिए किया जा सकता है खास ऐक्सेस. अगर दो थ्रेड "सिंक करने" की कोशिश करते हैं पूरी तरह कैसे उसी ऑब्जेक्ट को एक्सपोर्ट करता है, तो उनमें से एक तब तक इंतज़ार करेगा, जब तक कि दूसरा पूरा नहीं हो जाता.
जैसा कि हमने ऊपर बताया है, Java का volatile T
C++11 का atomic<T>
. एक ही समय पर इतने ऐक्सेस किए जा सकते हैं
volatile
फ़ील्ड की अनुमति है और इनकी वजह से डेटा के साथ काम नहीं होता.
lazySet()
और अन्य को अनदेखा किया जा रहा है. और डेटा रेस होती हैं, तो यह Java वीएम का काम है,
सुनिश्चित करें कि परिणाम अब भी क्रमिक रूप से एक जैसा दिखाई देता है.
खास तौर पर, अगर थ्रेड 1, volatile
फ़ील्ड पर लिखती है और
बाद में, थ्रेड 2 उसी फ़ील्ड से पढ़ती है और लिखे गए नए टेक्स्ट को देखती है
वैल्यू है, तो थ्रेड 2 में भी इस बात की गारंटी है कि वह पहले किए गए सभी लेख देख सकता है
थ्रेड 1. मेमोरी इफ़ेक्ट के मामले में,
बार-बार अपडेट होने वाला डेटा, मॉनिटर रिलीज़ की तरह है और
बार-बार अपडेट होना, मॉनिटर करने वाले उपयोगकर्ता की तरह होता है.
C++ के atomic
की तुलना में एक खास अंतर है:
अगर हम volatile int x;
लिखते हैं, तो
तो x++
, x = x + 1
के समान है; यह
ऐटॉमिक लोड करता है, नतीजे में बढ़ोतरी करता है, और फिर ऐटॉमिक लोड करता है
स्टोर. C++ के उलट, पूरे मामले में यह बढ़ोतरी ऐटमिक नहीं है.
इसके बजाय, ऐटॉमिक बढ़ोतरी वाली कार्रवाइयां इनके ज़रिए दी जाती हैं
java.util.concurrent.atomic
.
उदाहरण
यहां मोनोटोनिक काउंटर को आसानी से और गलत तरीके से लागू करने के बारे में बताया गया है: (Java थ्योरी और प्रैक्टिस: उतार-चढ़ाव मैनेज करना).
class Counter { private int mValue; public int get() { return mValue; } public void incr() { mValue++; } }
मान लें कि get()
और incr()
को एक से ज़्यादा फ़ील्ड से कॉल किया गया है
है. साथ ही, हम यह पक्का करना चाहते हैं कि जब भी हर थ्रेड, मौजूदा संख्या को देखे,
get()
पर कॉल किया गया है. सबसे बड़ी समस्या यह है कि
mValue++
असल में तीन ऑपरेशन हैं:
reg = mValue
reg = reg + 1
mValue = reg
अगर दो थ्रेड एक साथ incr()
में काम करते हैं, तो इनमें से एक
अपडेट खो सकते हैं. इंक्रीमेंट एटोमिक बनाने के लिए, हमें घोषणा करनी होगी
incr()
“सिंक किया गया”.
हालांकि, खास तौर पर एसएमपी में यह अब भी काम नहीं कर रहा है. डेटा की दौड़ अभी तो जारी है,
get()
इसके साथ-साथ mValue
को भी ऐक्सेस कर सकता है
incr()
. Java के नियमों के तहत, get()
कॉल
अन्य कोड के हिसाब से क्रम में लगाया जाता है. उदाहरण के लिए, अगर हम
नतीजे एक जैसे दिखाई देते हैं, तो नतीजे अलग-अलग दिख सकते हैं.
क्योंकि get()
कॉल को हमने हार्डवेयर या
कंपाइलर. हम get()
को यह एलान करके समस्या को ठीक कर सकते हैं:
सिंक किया गया. इस बदलाव के साथ, कोड साफ़ तौर पर सही है.
माफ़ करें, हमने लॉक के विवाद की सुविधा शुरू की है.
परफ़ॉर्मेंस को प्रभावित कर सकता है. get()
को तय करने के बजाय
सिंक होने के बाद, हम mValue
को “डेटा में उतार-चढ़ाव” के तौर पर एलान कर सकते हैं. (नोट
incr()
को अब भी synchronize
का इस्तेमाल करना होगा
हालांकि, mValue++
कोई एक ऐटॉमिक ऑपरेशन नहीं है.)
इससे डेटा के सभी फ़ंक्शन से बचा जा सकता है, इसलिए क्रम में एक जैसा अनुभव बनाए रखा जाता है.
incr()
कुछ हद तक धीमा हो जाएगा, क्योंकि इसके लिए मॉनिटर एंट्री/बाहर निकलने, दोनों तरीके अपनाए जाते हैं
से जुड़े होते हैं, लेकिन
get()
तेज़ी से काम करेगा, इसलिए विवाद न होने पर भी यह ऐसा होगा
लिखने के लिए बहुत ज़्यादा अंक पाने पर विजेता. (पूरी तरह से रास्ता देखने के लिए AtomicInteger
भी देखें
सिंक किया गया ब्लॉक हटाएं.)
यहां दिए गए C उदाहरणों की तरह ही, एक और उदाहरण देखें:
class MyGoodies { public int x, y; } class MyClass { static MyGoodies sGoodies; void initGoodies() { // runs in thread 1 MyGoodies goods = new MyGoodies(); goods.x = 5; goods.y = 10; sGoodies = goods; } void useGoodies() { // runs in thread 2 if (sGoodies != null) { int i = sGoodies.x; // could be 5 or 0 .... } } }
इसमें वही समस्या है जो C कोड में है,
sGoodies
को डेटा इकट्ठा होता है. इस तरह असाइनमेंट
sGoodies = goods
को शुरू करने से पहले देखा जा सकता है
goods
में फ़ील्ड. अगर आपने sGoodies
का एलान किया है, तो
volatile
कीवर्ड, एक क्रम में चलने वाले कीवर्ड को पहले जैसा कर दिया जाता है और चीज़ें काम करती रहेंगी
उम्मीद के मुताबिक.
ध्यान दें कि सिर्फ़ sGoodies
रेफ़रंस में बदलाव होता है. कॉन्टेंट बनाने
इसके अंदर मौजूद फ़ील्ड तक पहुंच नहीं करता है. sGoodies
होने के बाद
volatile
. साथ ही, मेमोरी के क्रम को सही तरीके से सुरक्षित रखा गया है, फ़ील्ड
को एक साथ ऐक्सेस नहीं किया जा सकता. z =
sGoodies.x
स्टेटमेंट से MyClass.sGoodies
का बार-बार लोड होगा
sGoodies.x
का नॉन-वोलेटाइल लोड होना चाहिए. अगर आपको
रेफ़रंस MyGoodies localGoods = sGoodies
है, तो इसके बाद का z =
localGoods.x
बार-बार होने वाले लोड नहीं करेगा.
Java प्रोग्रामिंग में एक सामान्य मुहावरे, “दोबारा जांच की गई” कुख्यात लॉक किया जा रहा है”:
class MyClass { private Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized (this) { if (helper == null) { helper = new Helper(); } } } return helper; } }
आइडिया यह है कि हम Helper
का एक इंस्टेंस रखना चाहते हैं
MyClass
के इंस्टेंस से जुड़ा ऑब्जेक्ट है. हमें केवल
इसलिए, हम इसे एक खास getHelper()
की मदद से बनाते हैं और वापस करते हैं
फ़ंक्शन का इस्तेमाल करना होगा. ऐसी रेस से बचने के लिए जिसमें दो थ्रेड इंस्टेंस बनाते हैं, हमें
ऑब्जेक्ट बनाने के लिए सिंक करता है. हालांकि, हम
“सिंक किए गए” ब्लॉक को हर कॉल में शामिल किया जाता है, इसलिए हम वह हिस्सा सिर्फ़ तब ही इस्तेमाल करते हैं, जब
helper
अभी खाली है.
इसमें helper
फ़ील्ड में डेटा रेस है. यह काम किया जा सकता है
किसी दूसरे थ्रेड में helper == null
के साथ-साथ सेट करें.
इसकी वजह जानने के लिए,
उसी कोड को थोड़ा-बहुत फिर से लिखा गया हो, जैसे कि उसे किसी C-जैसे भाषा में कंपाइल कर दिया गया हो
(Helper’s
को दिखाने के लिए मैंने कुछ पूर्णांक फ़ील्ड जोड़े हैं
कंस्ट्रक्टर गतिविधि):
if (helper == null) { synchronized() { if (helper == null) { newHelper = malloc(sizeof(Helper)); newHelper->x = 5; newHelper->y = 10; helper = newHelper; } } return helper; }
हार्डवेयर या कंपाइलर में से किसी एक को रोकने की कोई ज़रूरत नहीं है
helper
में स्टोर को फिर से क्रम में लगाने से लेकर
x
/y
फ़ील्ड. कोई दूसरा थ्रेड मिल सकता है
helper
खाली नहीं है, लेकिन इसके फ़ील्ड अभी सेट नहीं हैं और इस्तेमाल के लिए तैयार नहीं हैं.
गड़बड़ी के बारे में ज़्यादा जानकारी और गड़बड़ी वाले अन्य मोड के लिए, “‘दो बार सही का निशान लगाया गया’ सेक्शन देखें
आइटम को लॉक करने की सुविधा काम नहीं कर रही है” लिंक दिया गया है. इसमें ज़्यादा जानकारी के लिए या आइटम
71 ("लैज़ी इनिशलाइज़ेशन का इस्तेमाल समझदारी से करें"): Josh Bloch के प्रभावी Java,
दूसरा वर्शन..
इसे ठीक करने के दो तरीके हैं:
- आसान तरीके से काम करें और आउटर चेक को मिटा दें. इससे पक्का होता है कि हम
सिंक किए गए ब्लॉक के बाहर
helper
मान की जांच करें. helper
में बार-बार बदलाव करने की सूचना दें. इस छोटे से बदलाव के बाद, कोड उदाहरण के लिए, J-3 Java 1.5 और उसके बाद के वर्शन पर सही तरीके से काम करेगा. (हो सकता है कि आप एक मिनट में खुद को भरोसा दिलाएं कि यह सच है.)
यहां volatile
व्यवहार का एक और उदाहरण दिया गया है:
class MyClass { int data1, data2; volatile int vol1, vol2; void setValues() { // runs in Thread 1 data1 = 1; vol1 = 2; data2 = 3; } void useValues() { // runs in Thread 2 if (vol1 == 2) { int l1 = data1; // okay int l2 = data2; // wrong } } }
useValues()
को देखें, अगर थ्रेड 2 ने अब तक
अपडेट करने के बाद vol1
में अपडेट करता है, तो उसे यह पता नहीं चल सकता कि data1
या
data2
अभी तक सेट कर दिया गया है. जब उसे
vol1
, इसे पता है कि data1
को सुरक्षित तरीके से ऐक्सेस किया जा सकता है
और डेटा की रेस के बिना सही तरीके से पढ़ा जा सके. हालांकि,
यह data2
के बारे में कोई अनुमान नहीं लगा सकता, क्योंकि वह स्टोर
उतार-चढ़ाव वाले स्टोर के बाद परफ़ॉर्म करते हैं.
ध्यान दें कि दोबारा ऑर्डर करने से रोकने के लिए, volatile
का इस्तेमाल नहीं किया जा सकता
एक-दूसरे से रेस होती हैं. इसकी कोई गारंटी नहीं है
हम मशीन की मेमोरी फ़ेंस के लिए निर्देश जनरेट करते हैं. इसका इस्तेमाल, नुकसान पहुंचाने वाले कॉन्टेंट को फैलने से रोकने के लिए किया जा सकता है
कोड को लागू करके डेटा तब ही रेस करता है, जब कोई दूसरा थ्रेड
कोई तय शर्त होती है.
क्या करें
C/C++ में, C+11 विकल्प को चुनें
सिंक्रोनाइज़ेशन क्लास, जैसे कि std::mutex
. अगर ऐसा नहीं है, तो इसका इस्तेमाल करें
संबंधित pthread
कार्रवाइयां.
इनमें सही मेमोरी फ़ेंस शामिल हैं. साथ ही, जो सही तरीके से (एक जैसा) देते हैं
जब तक अलग से न बताया गया हो)
और सभी Android प्लैटफ़ॉर्म वर्शन पर बेहतर व्यवहार करने में मदद मिलती है. कृपया इनका इस्तेमाल करें
सही तरीके से. उदाहरण के लिए, याद रखें कि स्थिति वैरिएबल का इंतज़ार गलत तरीके से हो सकता है
वापस लौटाने के लिए कोई सिग्नल नहीं दिया जाता. इसलिए, यह एक लूप में दिखना चाहिए.
बेहतर होगा कि आप सीधे तौर पर एटॉमिक फ़ंक्शन का इस्तेमाल न करें, बशर्ते आपके पास इसे लागू करना बेहद आसान है. जैसे, कोई काउंटर. लॉक किया जा रहा है और पीथ्रेड म्यूटेक्स को अनलॉक करने के लिए, हर एक पर एक ही ऐटमिक ऑपरेशन की ज़रूरत होती है, और अक्सर एक कैश मिस होने पर भी कम लागत आती है, अगर कोई विवाद इसलिए है, ताकि आप म्यूटेक्स कॉल को एटॉमिक ऑपरेशन गैर-मामूली डेटा संरचनाओं के लिए लॉक-फ़्री डिज़ाइन की ज़रूरत होती है ज़्यादा सावधानी बरतने की ज़रूरत है, ताकि डेटा स्ट्रक्चर में ऊंचे लेवल की कार्रवाइयां की जा सकें पूरी तरह से ऐटमिक दिखता है (न कि सिर्फ़ उनके अणुओं के टुकड़े).
अगर आपने ऐटॉमिक ऑपरेशन इस्तेमाल किए हैं, तो
memory_order
... या lazySet()
से बेहतर परफ़ॉर्मेंस मिल सकती है
का फ़ायदा मिला है, लेकिन अभी तक हमने जितना बताया है उससे ज़्यादा जानकारी की ज़रूरत है.
इसका उपयोग करने वाले मौजूदा कोड का एक बड़ा हिस्सा
इसके बाद, हमें पता चलता है कि इनमें गड़बड़ियां हैं. अगर हो सके, तो इनसे बचें.
अगर आपके इस्तेमाल के उदाहरण अगले सेक्शन में दिए गए उदाहरणों में से किसी एक के हिसाब से नहीं हैं,
पक्का करें कि आप विशेषज्ञ हैं या आपने किसी विशेषज्ञ से सलाह ली है.
C/C++ में थ्रेड कम्यूनिकेशन के लिए, volatile
का इस्तेमाल करने से बचें.
Java में, समवर्ती सवालों को अक्सर
इसके लिए, सही यूटिलिटी क्लास इस्तेमाल करें
java.util.concurrent
पैकेज. कोड सही तरीके से लिखा गया है
SMP पर टेस्ट किया गया.
सबसे सुरक्षित तरीका यह है कि आप चीज़ों को नहीं बदले जा सकने वाले बनाएं. ऑब्जेक्ट जावा की स्ट्रिंग और पूर्णांक धारण डेटा जैसी क्लास से ऑब्जेक्ट बनाया जाता है, जिसमें उन ऑब्जेक्ट पर डेटा रेस की सभी संभावना शामिल नहीं होती है. किताब प्रभावी Java, दूसरा वर्शन के “आइटम 15: म्यूटेबिलिटी को कम से कम करें” में खास निर्देश दिए गए हैं. इसमें नोट खास तौर पर Java फ़ील्ड को “फ़ाइनल" बताने की अहमियत (ब्लोच).
भले ही, किसी ऑब्जेक्ट में बदलाव न किया जा सके, लेकिन याद रखें कि इसे किसी दूसरे ऑब्जेक्ट के साथ
बिना किसी तरह के सिंक नहीं होने वाला थ्रेड, डेटा की रेस है. ऐसा कभी-कभी हो सकता है
(नीचे देखें), लेकिन इसका बहुत ज़्यादा ध्यान रखने की ज़रूरत होती है, और इसके नतीजे
ब्रिटल कोड. अगर यह बहुत ज़्यादा ज़रूरी नहीं है, तो
volatile
एलान. C++ में, पॉइंटर शेयर करना या
के संदर्भ में नहीं बदला जा सकता.
डेटा की रेस की तरह, एक बग होता है.
इस स्थिति में, इसकी वजह से रुक-रुककर क्रैश होने की संभावना होती है, क्योंकि
उदाहरण के लिए, मैसेज पाने वाले थ्रेड को ऐसे तरीके की टेबल दिख सकती है जिसे शुरू न किया गया हो
पॉइंटर के रूप में नहीं बदला जा सकता.
यदि कोई मौजूदा लाइब्रेरी क्लास और न ही कोई नहीं बदला जा सकने वाला क्लास है
ठीक है, Java synchronized
स्टेटमेंट या C++
सुरक्षा के लिए, lock_guard
/ unique_lock
का इस्तेमाल किया जाना चाहिए
किसी भी फ़ील्ड को ऐक्सेस करता है जिसे एक से ज़्यादा थ्रेड से ऐक्सेस किया जा सकता है. अगर म्यूटक्स नहीं मिल रहे हैं, तो
काम करने के लिए हैं, तो आपको शेयर किए गए फ़ील्ड
volatile
या atomic
, लेकिन आपको सावधानी बरतनी होगी
एक-दूसरे से जुड़ी चीज़ों को समझने में मदद मिलती है. ये एलान लागू नहीं होंगे
आपको एक साथ प्रोग्राम में होने वाली आम गलतियों से बचाने में मदद मिलेगी. हालांकि, इससे आपको
कंपाइलर और एसएमपी को ऑप्टिमाइज़ करने से जुड़ी रहस्यमयी गड़बड़ियों से बचें
दुर्घटनाएं.
आपको ऐसा नहीं करना चाहिए "पब्लिश हो रहा है" किसी ऑब्जेक्ट के लिए रेफ़रंस, जैसे कि उसे दूसरे लोगों को उपलब्ध कराना थ्रेड को अपने कंस्ट्रक्टर में शामिल करें. C++ में यह कम ज़रूरी है या हमारी "कोई डेटा रेस नहीं" सलाह पाएं. लेकिन यह हमेशा एक अच्छी सलाह होती है, और ज़रूरी है, अगर आपका Java कोड अन्य ऐसे कॉन्टेक्स्ट में चलाए जाते हैं जिनमें Java का सुरक्षा मॉडल ज़रूरी है और जिन पर भरोसा नहीं किया जा सकता कोड "लीक हो चुके" डेटा को ऐक्सेस करके, डेटा रेस लागू कर सकता है ऑब्जेक्ट संदर्भ है. उस स्थिति में भी यह बेहद ज़रूरी है कि आप हमारी चेतावनियों को नज़रअंदाज़ करें और कुछ तकनीकों का इस्तेमाल करें पढ़ें. (Java में सुरक्षित निर्माण तकनीक) के बारे में ज़्यादा जानकारी के लिए विवरण
कमज़ोर मेमोरी ऑर्डर के बारे में ज़्यादा जानें
C+11 और इसके बाद के वर्शन में, खास स्थिति में काम आने वाले
डेटा-रेस-फ़्री प्रोग्राम के लिए एक जैसे होने की गारंटी देता है. अश्लील कॉन्टेंट
memory_order_relaxed
, memory_order_acquire
(लोड किए जाते हैं)
सिर्फ़) और ऐटॉमिक के लिए memory_order_release
(सिर्फ़ स्टोर) आर्ग्युमेंट
इनमें से हर एक ऑपरेशन, डिफ़ॉल्ट से कमतर गारंटी देता है. आम तौर पर,
इंप्लिसिट, memory_order_seq_cst
. memory_order_acq_rel
अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है
memory_order_acquire
और, दोनों उपलब्ध कराता है
एटॉमिक रीड-मॉडिफ़ाइंग राइटिंग के लिए memory_order_release
गारंटी
कार्रवाइयां. memory_order_consume
अभी काफ़ी नहीं है
उपयोगी होने के लिए अच्छी तरह बताया गया हो या लागू किया गया हो. फ़िलहाल, इसे अनदेखा किया जाना चाहिए.
Java.util.concurrent.atomic
में lazySet
के तरीके
C++ memory_order_release
स्टोर जैसे हैं. Java की
सामान्य चरों का इस्तेमाल कभी-कभी
memory_order_relaxed
बार ऐक्सेस किया गया. हालांकि, असल में ये
और कमज़ोर कर दिया. C++ के उलट, बिना क्रम के
volatile
के तौर पर तय किए गए वैरिएबल को ऐक्सेस करता है.
आम तौर पर, ऐसा तब तक नहीं करना चाहिए, जब तक परफ़ॉर्मेंस की कोई ठोस वजह न हो उनका इस्तेमाल कैसे हो सकता है. ARM जैसी कम क्षमता वाली मशीन आर्किटेक्चर पर, उनका इस्तेमाल करने पर आम तौर पर, ऐटॉमिक ऑपरेशन के लिए कुछ दर्जन मशीन साइकल के हिसाब से बचत होती है. x86 पर, परफ़ॉर्मेंस में सुधार सिर्फ़ स्टोर तक सीमित होता है और इसके कम होने की संभावना होती है ध्यान देने लायक. हालांकि, कुछ हद तक उलटा असर हो सकता है, लेकिन कोर की संख्या ज़्यादा होने पर, इससे होने वाले फ़ायदे में कमी आ सकती है. क्योंकि मेमोरी सिस्टम, सीमित करने वाले फ़ैक्टर बन जाता है.
कमज़ोर क्रम वाले एटॉमिक्स का मतलब समझना मुश्किल है. आम तौर पर, इनको ज़रूरी होता है भाषा के उन नियमों की सटीक समझ होना, जिन्हें हम इस बारे में ज़्यादा जानकारी चाहिए. उदाहरण के लिए:
- कंपाइलर या हार्डवेयर,
memory_order_relaxed
को दूसरी जगह ले जा सकता है लॉक के दायरे में आने वाले अहम सेक्शन को ऐक्सेस करता है (लेकिन उससे बाहर नहीं) उपयोगकर्ता हासिल करना और उन्हें रिलीज़ करना. इसका मतलब है कि दो ऐसा हो सकता है किmemory_order_relaxed
स्टोर, सही क्रम में न दिखें, भले ही उन्हें किसी ज़रूरी सेक्शन से अलग किया गया हो. - किसी सामान्य Java वैरिएबल का इस्तेमाल, शेयर किए गए काउंटर के तौर पर किए जाने पर, वह दिख सकता है
को कम करने के लिए किसी दूसरे थ्रेड में जोड़ें, भले ही वह सिर्फ़ एक
अन्य थ्रेड. हालांकि, यह C++ ऐटॉमिक के मामले में सही नहीं है
memory_order_relaxed
.
ध्यान रखें कि चेतावनी के तौर पर, यहां हम कुछ मुहावरे देते हैं. के केस, कमज़ोर क्रम वाले एटॉमिक्स के हो सकते हैं. इनमें से कई विकल्प सिर्फ़ C++ पर लागू होते हैं.
रेसिंग गेम ऐक्सेस न करें
वैरिएबल आम तौर पर एटॉमिक होता है, क्योंकि कभी-कभी ऐसा नहीं होता
लिखने के साथ-साथ पढ़ने के लिए उपलब्ध है. हालांकि, यह समस्या सभी ऐक्सेस में नहीं आती.
उदाहरण के लिए, वैरिएबल
परमाणु हो सकता है, क्योंकि इसे एक महत्वपूर्ण सेक्शन के बाहर पढ़ा जाता है, लेकिन सभी
अपडेट को लॉक करके सुरक्षित रखा जाता है. इस स्थिति में, कोई रीड
एक ही लॉक से सुरक्षित
रेस नहीं की जा सकती, क्योंकि एक साथ लिखने की सुविधा नहीं हो सकती. ऐसी स्थिति में,
नॉन-रेसिंग ऐक्सेस (इस मामले में लोड करें), के साथ एनोटेट किया जा सकता है
C++ कोड की शुद्धता को बदले बिना memory_order_relaxed
.
लॉक लागू करने पर, ज़रूरी मेमोरी को क्रम में लगाने की सुविधा पहले से ही लागू होती है
जहां दूसरे थ्रेड से ऐक्सेस किया गया हो, और memory_order_relaxed
यह बताता है कि ज़रूरी नहीं है कि किसी अतिरिक्त ऑर्डरिंग कंस्ट्रेंट को
एटोमिक ऐक्सेस के लिए लागू किया गया था.
Java में इसका कोई वास्तविक एनालॉग नहीं है.
जानकारी के सटीक होने के लिए, नतीजे पर भरोसा नहीं किया जाता
जब हम सिर्फ़ संकेत जनरेट करने के लिए रेसिंग लोड का इस्तेमाल करते हैं, तब आम तौर पर यह भी ठीक होता है
लोड करने के लिए कोई मेमोरी क्रम लागू न करने के लिए. अगर मान यह है
नहीं हैं, हम नतीजे के बारे में भरोसेमंद सोर्स से कोई अनुमान नहीं
अन्य वैरिएबल की संख्या डालें. तो यह ठीक है
अगर मेमोरी के क्रम की गारंटी नहीं है और लोड
memory_order_relaxed
आर्ग्युमेंट के साथ दी गई.
एक कॉमन
उदाहरण के लिए, C++ compare_exchange
का इस्तेमाल करना
x
को f(x)
से अपने आप बदलने के लिए.
f(x)
कंप्यूट करने के लिए x
का शुरुआती लोड
भरोसेमंद होना ज़रूरी नहीं है. अगर हम गलत होते हैं,
compare_exchange
से प्रोसेस पूरी नहीं हो पाएगी और हम फिर से कोशिश करेंगे.
x
के शुरुआती लोड के लिए इसका इस्तेमाल करना ठीक है
memory_order_relaxed
आर्ग्युमेंट; सिर्फ़ मेमोरी को क्रम में लगाने की सुविधा
compare_exchange
मामलों के लिए.
अपने आप बदला गया लेकिन नहीं पढ़ा गया डेटा
कभी-कभी डेटा में कई थ्रेड के साथ-साथ बदलाव किया जाता है, लेकिन
जांच नहीं की जाती, जब तक पैरलल कंप्यूटेशन की प्रोसेस पूरी नहीं हो जाती. बहुत अच्छा
इसका उदाहरण एक काउंटर है जो अपने आप बढ़ने वाला है (उदाहरण के लिए,
C++ में fetch_add()
का इस्तेमाल करके या
atomic_fetch_add_explicit()
C में) एक साथ कई थ्रेड के साथ, लेकिन इन कॉल का नतीजा
को हमेशा अनदेखा कर दिया जाता है. इससे मिलने वाली वैल्यू को आखिर में पढ़ा जाता है,
सभी अपडेट के पूरा होने के बाद.
इस मामले में, यह पता लगाने का कोई तरीका नहीं है कि इस डेटा को ऐक्सेस किया जा सकता है या नहीं
दोबारा ऑर्डर किया गया था. इसलिए, C++ कोड memory_order_relaxed
का इस्तेमाल कर सकता है
तर्क है.
सामान्य इवेंट काउंटर इसका एक सामान्य उदाहरण हैं. क्योंकि यह इतना सामान्य है, तो इस मामले में कुछ टिप्पणियां करना ज़रूरी है:
-
memory_order_relaxed
का इस्तेमाल करने से परफ़ॉर्मेंस बेहतर होती है, हालांकि, ऐसा हो सकता है कि इससे परफ़ॉर्मेंस की सबसे अहम समस्या हल न हो पाए: हर अपडेट के बारे में जानकारी काउंटर को होल्ड करने वाली कैश लाइन तक खास ऐक्सेस की ज़रूरत होती है. यह से हर बार कोई नया थ्रेड, काउंटर को ऐक्सेस करने पर कैश मेमोरी में सेव नहीं होता. अगर अपडेट बार-बार होते हैं और थ्रेड के बीच में वैकल्पिक होते हैं, तो यह ज़्यादा तेज़ी से काम करता है शेयर किए गए काउंटर को हर बार अपडेट करने से बचने के लिए, उदाहरण के लिए, थ्रेड-लोकल काउंटर का इस्तेमाल करना और उन्हें आखिर में जोड़ना. - इस तकनीक को पिछले सेक्शन के साथ जोड़ा जा सकता है:
अनुमानित और गैर-भरोसेमंद वैल्यू को एक साथ पढ़ा जाता है, जबकि उन्हें अपडेट किया जाता है,
memory_order_relaxed
का इस्तेमाल करके सभी कार्रवाइयों के साथ. हालांकि, यह ज़रूरी है कि आप जो मानें, वे पूरी तरह से गैर-भरोसेमंद हों. सिर्फ़ एक बार गिनती बढ़ने से ऐसा नहीं होता इसका मतलब है कि तीसरे पक्ष के थ्रेड को जिस पर बढ़ोतरी हुई है. इसके बजाय, बढ़ोतरी में को पिछले कोड से फिर से क्रम में लगाया गया है. (जैसा कि हमने बताया है उसी तरह के मामले में पहले, C++ इस बात की गारंटी देता है कि ऐसे काउंटर का दूसरा लोड उसी थ्रेड में पहले के लोड से कम वैल्यू दिखाता है. जब तक काउंटर ओवरफ़्लो हो गया.) - अनुमानित संख्या का पता लगाने की कोशिश करने वाला कोड मिलना आम बात है अलग-अलग ऐटॉमिक (या नहीं) पढ़ने-लिखने में मदद करके, मानों की गणना करें, लेकिन इससे रेवेन्यू में बढ़ोतरी नहीं हो रही है. सामान्य तर्क यह है कि यह "काफ़ी हद तक" है परफ़ॉर्मेंस काउंटर या इस तरह की अन्य चीज़ों के लिए. आम तौर पर, ऐसा नहीं होता है. जब अपडेट काफ़ी बार होते हैं (उदाहरण के लिए जो आपके लिए अहम हैं), उनकी संख्या का एक बड़ा हिस्सा आम तौर पर खो गया. क्वाड कोर डिवाइस पर, आम तौर पर आधे से ज़्यादा आंकड़े गायब हो सकते हैं. (आसान व्यायाम: दो थ्रेड की स्थिति बनाएं जिसमें काउंटर 10 लाख बार अपडेट किया गया है, लेकिन आखिरी काउंटर वैल्यू एक है.)
सिंपल फ़्लैग कम्यूनिकेशन
कोई memory_order_release
स्टोर (या पढ़ने-में-बदलाव करने-लिखने की कार्रवाई)
पक्का करता है कि अगर
memory_order_acquire
(या पढ़ें-बदलें-लिखें कार्रवाई) लिखित मान को पढ़ता है, तो यह
आप उन सभी स्टोर (सामान्य या एटॉमिक) का भी ध्यान रखें जो
memory_order_release
स्टोर. इसके उलट, किसी भी लोड
memory_order_release
से पहले के नतीजे के लिए,
memory_order_acquire
के लोड होने के बाद स्टोर.
memory_order_relaxed
के उलट, यह इस तरह के ऐटमिक ऑपरेशन को मंज़ूरी देता है
का इस्तेमाल, एक थ्रेड की प्रोग्रेस को दूसरे थ्रेड के साथ शेयर करने के लिए किया जा सकता है.
उदाहरण के लिए, हम दोबारा चेक किए गए लॉक के उदाहरण को फिर से लिख सकते हैं इस तरह C++ में ऊपर से
class MyClass { private: atomic<Helper*> helper {nullptr}; mutex mtx; public: Helper* getHelper() { Helper* myHelper = helper.load(memory_order_acquire); if (myHelper == nullptr) { lock_guard<mutex> lg(mtx); myHelper = helper.load(memory_order_relaxed); if (myHelper == nullptr) { myHelper = new Helper(); helper.store(myHelper, memory_order_release); } } return myHelper; } };
उपयोगकर्ता हासिल करने के लोड और रिलीज़ स्टोर से यह पक्का होता है कि अगर हमें कोई ऐसी वैल्यू दिखती है जो शून्य नहीं है
helper
है, तो हमें यह भी दिखेगा कि इसके फ़ील्ड सही तरीके से शुरू हुए हैं.
हमने इस बात से पहले की निगरानी में यह भी शामिल किया है कि नॉन-रेसिंग लोड
memory_order_relaxed
का इस्तेमाल कर सकते हैं.
कोई Java प्रोग्रामर, helper
को संभावित रूप से
java.util.concurrent.atomic.AtomicReference<Helper>
और रिलीज़ स्टोर के तौर पर lazySet()
का इस्तेमाल करें. लोड
कार्रवाइयां, सामान्य get()
कॉल का इस्तेमाल करती रहेंगी.
दोनों ही मामलों में, परफ़ॉर्मेंस में हुए बदलाव को ध्यान में रखते हुए, शुरुआती पाथ, जो प्रदर्शन के लिए महत्वपूर्ण होने की संभावना नहीं है. एक और पठनीय छेड़छाड़ यह हो सकती है कि:
Helper* getHelper() { Helper* myHelper = helper.load(memory_order_acquire); if (myHelper != nullptr) { return myHelper; } lock_guard<mutex> lg(mtx); if (helper == nullptr) { helper = new Helper(); } return helper; }
यह तरीका तेज़ी से लोड होने की सुविधा देता है, लेकिन डिफ़ॉल्ट रूप से क्रम के मुताबिक लगातार होने वाला, नॉन-परफ़ॉर्मेंस-क्रिटिकल स्लो पर ऑपरेशन पाथ.
यहां भी, helper.load(memory_order_acquire)
मौजूदा Android पर काम करने वाले वर्शन पर समान कोड जनरेट करने की संभावना है
आर्किटेक्चर
helper
. हमारे लिए सबसे फ़ायदेमंद ऑप्टिमाइज़ेशन होना चाहिए
पहली बार myHelper
में शुरुआत की जा सकती है
दूसरी लोडिंग भी होती है, हालांकि भविष्य का कोई कंपाइलर अपने आप ऐसा कर सकता है.
ऑर्डर लेने/रिलीज़ करने के ऑर्डर से स्टोर, लोगों को आपका स्टोर देखने से नहीं रोका जा सकता
देरी से डिलीवर किया गया हो. साथ ही, इससे यह पक्का नहीं होता कि स्टोर, दूसरे थ्रेड को दिखें
एक क्रम में. इस वजह से, यह पेचीदा,
हालांकि, यह काफ़ी सामान्य कोडिंग पैटर्न है, जिसे डेकर के आपसी बहिष्करण का उदाहरण दिया गया है.
एल्गोरिदम: सभी थ्रेड पहले एक फ़्लैग सेट करते हैं, जो बताता है कि वे करना चाहते हैं
कुछ; अगर कोई थ्रेड t है, तो देखें कि कोई दूसरी थ्रेड
कोई काम करने की कोशिश करते हुए, वह सुरक्षित रूप से आगे बढ़ सकता है.
रुकावट नहीं होगी. कोई और थ्रेड नहीं जोड़ा जाएगा
आगे नहीं बढ़ा जा सकता, क्योंकि t का फ़्लैग अब भी सेट है. यह प्रोसेस पूरी नहीं हुई
अगर 'उपयोगकर्ता हासिल करें/रिलीज़ करें' ऑर्डर का इस्तेमाल करके फ़्लैग को ऐक्सेस किया जाता है, तो ऐसा नहीं होता
लोगों को थ्रेड का फ़्लैग देर से दिखाने से रोकता है, क्योंकि उनके पास
जो गलती से आगे बढ़ गया. डिफ़ॉल्ट memory_order_seq_cst
उसे रोकता है.
नहीं बदले जा सकने वाले फ़ील्ड
अगर पहली बार इस्तेमाल करने पर कोई ऑब्जेक्ट फ़ील्ड शुरू होता है और फिर कभी नहीं बदला जाता है, तो
शुरू किया जा सकता है और बाद में कमज़ोर तरीके से इस्तेमाल करके इसे पढ़ा जा सकता है
ऐक्सेस किया जा सकता है. C++ में, इसका एलान atomic
के तौर पर किया जा सकता है
और memory_order_relaxed
या Java का इस्तेमाल करके, इसे ऐक्सेस किया जा सकता है
volatile
के बिना एलान किया जा सकता है और बिना
खास उपायों को अपनाना होगा. इसके लिए इन सभी शर्तों को पूरा करना ज़रूरी है:
- फ़ील्ड की वैल्यू से ही बता पाना मुमकिन होना चाहिए यह पहले ही शुरू हो चुका है. फ़ील्ड को ऐक्सेस करने के लिए, फ़ास्ट पाथ की जांच और रिटर्न वैल्यू को फ़ील्ड को सिर्फ़ एक बार पढ़ना चाहिए. Java में, बाद वाली वैल्यू का होना ज़रूरी है. भले ही फ़ील्ड, शुरू किए गए तरीके से टेस्ट करके, दूसरा लोड, उस वैल्यू को पढ़ सकता है जिसे शुरू नहीं किया गया था. C++ में "एक बार पढ़ें" नियम बस एक अच्छा तरीका नहीं है.
- इनीशियलाइज़ेशन और बाद के लोड, दोनों ऐटॉमिक होने चाहिए,
अपडेट नहीं दिखना चाहिए. Java के लिए, फ़ील्ड
long
याdouble
नहीं होना चाहिए. C++ के लिए, एटॉमिक असाइनमेंट ज़रूरी है; उसे अपनी जगह पर बनाना काम नहीं करेगा, क्योंकिatomic
की बनावट, ऐटमिक नहीं है. - बार-बार शुरू करना सुरक्षित होना चाहिए, क्योंकि कई थ्रेड में ऐसा होता है शुरू नहीं की गई वैल्यू को एक साथ पढ़ सकता है. C++ में, आम तौर पर यह "मामूली रूप से कॉपी किए जा सकने वाले" शब्द से लिया गया है सभी के लिए लागू की गई शर्त ऐटॉमिक टाइप; नेस्ट किए गए मालिकाना हक वाले पॉइंटर वाले टाइप को डील की जगह कॉपी करने की ज़रूरत नहीं है और न ही इसे कॉपी करने योग्य बनाया जा सकता है. Java के लिए, कुछ खास तरह के रेफ़रंस स्वीकार किए जाते हैं:
- Java संदर्भ, ऐसे बदले जा सकने वाले टाइप तक सीमित हैं जिनमें सिर्फ़ फ़ाइनल शामिल है फ़ील्ड. नहीं बदले जा सकने वाले कंस्ट्रक्टर को पब्लिश नहीं करना चाहिए ऑब्जेक्ट का रेफ़रंस होता है. इस मामले में, Java के फ़ाइनल फ़ील्ड के नियम पक्का करें कि अगर कोई पाठक रेफ़रंस को देखता है, तो वह शुरू किए गए फ़ाइनल फ़ील्ड. C++ में इन नियमों का कोई एनालॉग नहीं है और मालिकाना हक वाले ऑब्जेक्ट के पॉइंटर इस वजह से भी स्वीकार नहीं किए जाएंगे (इसमें साथ ही, उन समाचार संगठनों को शर्तें).
वीडियो के आखिरी हिस्से के नोट
यह दस्तावेज़ सिर्फ़ सतह को स्क्रैच नहीं करता, बल्कि कम गहराई वाले गॉज से ज़्यादा मैनेज करें. यह एक बहुत बड़ा और गंभीर विषय है. कुछ सूचनाएं मिल रही हैं इन विषयों से जुड़ा जा सकता है:
- असल Java और C++ मेमोरी मॉडल,
इससे पहले होता है संबंध, जो बताता है कि दो कार्रवाइयों की गारंटी कब होती है
किसी खास क्रम में होता है. जब हम डेटा की रेस के बारे में बताते हैं, तो हम अनौपचारिक रूप से
ने "एक साथ" होने वाले दो मेमोरी ऐक्सेस के बारे में बात की.
आधिकारिक तौर पर, इसका मतलब यह है कि कोई भी ऐसी घटना एक-दूसरे से पहले नहीं हो रही है.
इससे पहले की वास्तविक परिभाषाएं जानना एक निर्देश देता है
और Java या C++ मेमोरी मॉडल में के साथ सिंक करता है.
हालांकि, इसका मतलब है कि "एक साथ" आम तौर पर अच्छा रहता है
ये परिभाषाएं निर्देश देने वाली होती हैं. खास तौर पर तब, जब
C++ में कमज़ोर क्रम वाले ऐटॉमिक ऑपरेशन इस्तेमाल करने पर विचार कर रहे हैं.
(मौजूदा Java स्पेसिफ़िकेशन सिर्फ़
lazySet()
के बारे में बताता है अनौपचारिक रूप से.) - यह एक्सप्लोर करें कि कोड को फिर से ऑर्डर करते समय, कंपाइलर क्या कर सकते हैं और क्या नहीं. (जेएसआर-133 की खास बातों में, कानूनी बदलावों के कुछ बेहतरीन उदाहरण मौजूद हैं. इन बदलावों की वजह से अनचाहे नतीजे.)
- Java और C++ में, नहीं बदले जा सकने वाले क्लास लिखने का तरीका जानें. (इसमें और भी बहुत कुछ है न कि सिर्फ़ “बनाने के बाद कुछ भी न बदलें”.
- प्रभावी Java, दूसरा वर्शन. (उदाहरण के लिए, आपको कॉल करने के ऐसे तरीके नहीं देने चाहिए जो जिसे किसी सिंक किए गए ब्लॉक में ओवरराइड किया जाना हो.)
- उपलब्ध सुविधाओं के बारे में जानने के लिए,
java.util.concurrent
औरjava.util.concurrent.atomic
एपीआई को पढ़ें. इसलिए, एक साथ कई एनोटेशन दिखाने की सुविधा, जैसे कि@ThreadSafe
और@GuardedBy
(net.jcip.annotations से).
अपेंडिक्स सेक्शन में, आगे की रीडिंग सेक्शन में दस्तावेज़ों और वेबसाइटों पर विज्ञापन दिखा सकते हैं.
अन्य जानकारी
सिंक्रोनाइज़ेशन स्टोर लागू करना
(ज़्यादातर प्रोग्रामर इसे लागू करने के लिए तैयार नहीं हैं. लेकिन यह चर्चा जोशीला है.)
छोटे बिल्ट-इन टाइप, जैसे कि int
और हार्डवेयर के लिए
Android, साधारण लोड और स्टोर निर्देशों से यह पक्का होता है कि
या तो पूरी तरह से या बिलकुल भी नहीं
प्रोसेसर उसी जगह को लोड कर रहा है. इस तरह, कुछ बुनियादी राय
"ऐटॉमिसिटी" का को मुफ़्त में उपलब्ध कराया जाता है.
जैसा कि हमने पहले देखा, यह तरीका काफ़ी नहीं है. यह पक्का करने के लिए कि सभी कीवर्ड हमें कार्रवाइयों का क्रम बदलने से रोकना होगा. साथ ही, यह पक्का करना होगा कि कि मेमोरी से जुड़ी कार्रवाइयां, अन्य प्रोसेस में एक जैसी ऑर्डर. हालांकि, Android वाले डिवाइसों पर यह अपने-आप काम करता है जिसमें हम एआई मॉडल का इस्तेमाल करने वाले सॉफ़्टवेयर, इसलिए, हम इस पर काफ़ी हद तक ध्यान नहीं देते.
मेमोरी की कार्रवाइयों का ऑर्डर, दोनों को फिर से क्रम में आने से रोकता है कंपाइलर की मदद से ऐसा करता है और हार्डवेयर से दोबारा ऑर्डर करने से रोकता है. हम यहां फ़ोकस करते हैं .
ARMv7, x86, और MIPS पर मेमोरी को क्रम में लगाने की सुविधा
"बाड़" के लिए निर्देश देता है
बाड़ के दिशा-निर्देशों का पालन करने वाले निर्देशों को दिखने से रोकें
बाड़ से पहले के निर्देशों से पहले. (ये आम तौर पर,
जिसे "बैरियर" कहते हैं लेकिन ऐसा करने से उपयोगकर्ता की
pthread_barrier
-स्टाइल वाली रुकावटें, जो बहुत कुछ करती हैं
से अलग.) CANNOT TRANSLATE
बाड़ों के लिए निर्देश एक बहुत ही जटिल विषय है, जिसे ठीक करना एक मुश्किल काम है
इस तरह से, अलग-अलग तरह के बाड़ लगाने की गारंटी दी जाती है
कैसे इंटरैक्ट करते हैं और ये आम तौर पर, ऑर्डर करने की अन्य गारंटी के साथ कैसे जुड़ते हैं
हार्डवेयर उपलब्ध कराया जाता है. यह काफ़ी अहम जानकारी है. इसलिए, हम
इन चीज़ों को अच्छी तरह से समझ लें.
ऑर्डर करने की सबसे बुनियादी गारंटी, C++
memory_order_acquire
और memory_order_release
ऐटॉमिक ऑपरेशन: किसी रिलीज़ स्टोर से पहले की मेमोरी की कार्रवाइयां
उपयोगकर्ता हासिल करने के लोड होने के बाद दिखना चाहिए. ARMv7 पर, यह
लागू करने वाला:
- बाड़ लगाने के सही निर्देश के साथ स्टोर निर्देश से पहले. यह सभी पुरानी मेमोरी ऐक्सेस को स्टोर निर्देश. (यह अनावश्यक रूप से में स्टोर निर्देश.)
- बाड़ लगाने के सही निर्देश के साथ, लोड करने के निर्देश का पालन करना, इससे लोड को अगली ऐक्सेस के साथ फिर से क्रम में लगाए जाने से रोका जा सकता है. (और एक बार फिर से बिना ज़रूरत के कम से कम पुराने लोड के साथ दिया जा रहा है.)
यह C++ इस्तेमाल करने/रिलीज़ ऑर्डर करने के लिए काफ़ी है.
ये Java volatile
के लिए ज़रूरी हैं, लेकिन काफ़ी नहीं हैं
या C++ क्रम से एक जैसा atomic
.
यह जानने के लिए कि हमें और किन चीज़ों की ज़रूरत है, Dekker के एल्गोरिदम के हिस्से पर गौर करें
जिसके बारे में हम पहले ही बता चुके हैं.
flag1
और flag2
, C++ atomic
हैं
या Java volatile
वैरिएबल, दोनों शुरुआत में गलत हैं.
थ्रेड 1 | थ्रेड 2 |
---|---|
flag1 = true |
flag2 = true |
क्रम के मुताबिक एक जैसे होने का मतलब है कि एक असाइनमेंट में
flag
n को सबसे पहले एक्ज़ीक्यूट करना चाहिए और इसे
को दूसरे थ्रेड में टेस्ट करें. इसलिए, हम
ये थ्रेड एक साथ "अहम-सामान" को एक्ज़ीक्यूट करती हैं.
हालांकि, रिलीज़-रिलीज़ के क्रम के लिए ज़रूरी तलवारबाज़ी
हर थ्रेड की शुरुआत और आखिर में बाड़ होनी चाहिए. इससे कोई मदद नहीं मिलती
यहां. साथ ही, हमें यह पक्का करना होगा कि अगर
atomic
में से volatile
स्टोर के बाद, आता है
volatile
/atomic
लोड होते हैं, तो दोनों का क्रम नहीं बदला जाता.
आम तौर पर किसी फ़ेंस को जोड़ने से
यह एक ही स्टोर में स्टोर करता है. यह बाद में भी इस्तेमाल होता है.
(यह फिर से ज़रूरत से ज़्यादा मज़बूत है, क्योंकि आम तौर पर यह बाड़ ऑर्डर करती है
बाद की सभी मेमोरी ऐक्सेस करने वाली मेमोरी को ऐक्सेस नहीं किया जा सकेगा.)
इसके बजाय, हम अतिरिक्त बाड़ को क्रम से एक जैसा लोड होना चाहिए. दुकानों की संख्या कम होती है, इसलिए यह तरीका अपनाया जाता है आम तौर पर, Android डिवाइसों के लिए इसका इस्तेमाल किया जाता है.
जैसा कि हमने पिछले सेक्शन में देखा था, हमें स्टोर/लोड बैरियर डालने की ज़रूरत होती है पर नज़र रखें. बार-बार अपडेट होने वाले ऐक्सेस के लिए, वीएम में एक्ज़ीक्यूट किया गया कोड कुछ ऐसा दिखेगा:
बार-बार होने वाला लोड | लगातार अपडेट होने वाला स्टोर |
---|---|
reg = A |
fence for "release" (2) |
आम तौर पर, रीयल मशीन आर्किटेक्चर कई तरह के जिससे अलग-अलग तरह के डेटा को ऐक्सेस किया जा सकता है और कीमतें अलग-अलग हों. इनमें से एक चुनना आसान है और इससे आपको अलग-अलग इसके लिए, उसे यह पक्का करना होगा कि स्टोर एक समान क्रम है, और यह कि मेमोरी का क्रम, कई बाड़ों का मिला-जुला रूप. ज़्यादा जानकारी के लिए, कृपया के साथ कैम्ब्रिज यूनिवर्सिटी का पेज देखें असली प्रोसेसर के लिए एटॉमिक्स की इकट्ठा की गई मैपिंग.
कुछ आर्किटेक्चर पर, खास तौर पर x86, "उपयोगकर्ता हासिल करना" और "रिलीज़" हो सकता है बाधाएं अनावश्यक होती हैं, क्योंकि हार्डवेयर हमेशा अस्पष्ट रूप से काफ़ी ऑर्डरिंग लागू करता है. इस तरह, x86 पर सिर्फ़ आखिरी बाड़ (3) जनरेट होती है. इसी तरह, x86 पर, ऐटॉमिक पढ़ें-बदलाव करें-लिखें कार्रवाइयों का मतलब मज़बूत बाड़ है. इसलिए ये कभी नहीं लगाने के लिए किसी भी बाड़ की ज़रूरत होती है. हमने ARMv7 पर, ऊपर बताए गए सभी बाड़ों पर आवश्यक.
ARMv8, LDAR और STLR के लिए निर्देश देता है, जो यह नीति, Java में लगातार अपडेट होने वाले या C++ और लगातार एक जैसे होने पर लागू होने वाली शर्तों को लागू करती है लोड और स्टोर. ये फिर से क्रम में लगाने की ग़ैर-ज़रूरी सीमाओं से बचाता है और हम जिनका ज़िक्र ऊपर किया गया है. ARM पर मौजूद 64-बिट Android कोड इनका इस्तेमाल करता है; हमने चुना हम ARMv7 बाड़ लगाने की जगह पर फ़ोकस करना चाहते हैं, क्योंकि यह सुरक्षा के लिए सबसे ज़्यादा सभी ज़रूरी शर्तों को पूरा करना होगा.
इसके बारे में और पढ़ें
ज़्यादा गहराई या विस्तार से जानकारी देने वाले वेब पेज और दस्तावेज़. सामान्य तौर पर ज़्यादा काम की लेख सूची में सबसे ऊपर हैं.
- शेयर की गई मेमोरी की क्षमता बनाए रखने वाले मॉडल: एक ट्यूटोरियल
- 1995 में Adve &की ओर से लिखा गया घराचोर्लू, अगर आपको मेमोरी कंसिस्टेंसी मॉडल के बारे में ज़्यादा जानना है, तो इसकी शुरुआत करें.
http://www.hpl.hp.com/techreports/Compaq-dec/WRL-95-7.pdf - मेमोरी की रुकावटें
- समस्याओं के बारे में खास जानकारी देने वाला छोटा सा लेख.
https://en.wikipedia.org/wiki/Memory_barrier - Threads से जुड़ी बुनियादी बातें
- C++ और Java में मल्टी-थ्रेड प्रोग्रामिंग की जानकारी. इसमें हैंस बोहेम की पेशकश है. डेटा रेस और सिंक करने के बुनियादी तरीकों के बारे में जानकारी.
http://www.hboehm.info/c++mm/threadsintro.html - Java कॉन करंसी का इस्तेमाल
- साल 2006 में पब्लिश हुई इस किताब में, कई तरह के विषयों पर विस्तार से बात की गई है. Java में मल्टी-थ्रेड कोड लिखने वाले किसी भी व्यक्ति के लिए खास तौर पर इसका सुझाव दिया जाता है.
http://www.javaconcurrencyinpractice.com - JSR-133 (Java मेमोरी मॉडल) के बारे में अक्सर पूछे जाने वाले सवाल
- Java मेमोरी मॉडल के बारे में सामान्य जानकारी, जिसमें सिंक्रोनाइज़ेशन, डेटा बार-बार अपडेट होने वाले वैरिएबल, और फ़ाइनल फ़ील्ड बनाने की जानकारी शामिल है.
(थोड़ी पुराना. खास तौर पर, जब अन्य भाषाओं के बारे में बात की जा रही हो.)
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html - Java मेमोरी मॉडल में प्रोग्राम ट्रांसफ़ॉर्मेशन की वैधता
- अन्य समस्याओं के बारे में तकनीकी जानकारी
Java का मेमोरी मॉडल. ये समस्याएं, डेटा-रेस-फ़्री मोड पर लागू नहीं होती हैं
प्रोग्राम में शामिल हो.
http://citationseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.112.1790&Rep=Rep1&type=pdf - java.util.concurrent पैकेज की खास जानकारी
java.util.concurrent
पैकेज के लिए दस्तावेज़. पेज के सबसे नीचे, "मेमोरी कंसिस्टेंसी प्रॉपर्टी" नाम का एक सेक्शन होता है, जिसमें अलग-अलग क्लास से मिलने वाली गारंटी के बारे में बताया जाता है.java.util.concurrent
पैकेज की खास जानकारी- Java थ्योरी और प्रैक्टिस: Java में सुरक्षित निर्माण की तकनीकें
- इस लेख में, ऑब्जेक्ट बनाते समय रेफ़रंस एस्केपिंग से जुड़े खतरों की जांच की गई है. साथ ही, थ्रेड को सुरक्षित करने वाले कंस्ट्रक्टर के लिए दिशा-निर्देश भी दिए गए हैं.
http://www.ibm.com/developerworks/java/library/j-jtp0618.html - Java थ्योरी ऐंड प्रैक्टिस: मैनेजिंग वोलैटिलिटी
- एक अच्छा लेख, जिसमें यह बताया गया है कि Java में बार-बार अपडेट होने वाले फ़ील्ड की मदद से क्या-क्या हासिल किया जा सकता है और क्या नहीं.
http://www.ibm.com/developerworks/java/library/j-jtp06197.html - “दो बार सही का निशान लगाकर लॉक करने की सुविधा सही तरीके से काम नहीं कर रही है” का एलान
- बिल पुग ने उन अलग-अलग तरीकों के बारे में पूरी जानकारी दी है जिनकी मदद से, दोबारा चेक की गई लॉक करने की प्रोसेस को
volatile
याatomic
के बिना पूरा नहीं किया जा सकता. C/C++ और Java शामिल हैं.
http://www.cs.umd.edu/~pugh/java/memoryModel/ब्लूटूथ CheckedLocking.html - [ARM] बैरियर लिटमस टेस्ट और कुकबुक
- ARM SMP की समस्याओं पर चर्चा, जिसमें ARM कोड के छोटे-छोटे स्निपेट शामिल किए गए हैं. अगर आपको इस पेज पर दिए गए उदाहरणों के बारे में ज़्यादा जानकारी नहीं है या आपको डीएमबी निर्देश का औपचारिक ब्यौरा पढ़ना है, तो इसे पढ़ें. यह एक्ज़ीक्यूटेबल कोड पर मेमोरी संबंधी रुकावटों के लिए इस्तेमाल किए जाने वाले निर्देशों के बारे में भी बताता है (यह तब काम आ सकता है, जब कोड को तुरंत जनरेट किया जा रहा हो). ध्यान दें कि यह ARMv8 से पहले का है, जो
मेमोरी को क्रम में लगाने के अतिरिक्त निर्देश इस्तेमाल किए जा सकते हैं और इन्हें बेहतर बनाया गया है
मेमोरी मॉडल में सेव किया जाएगा. ज़्यादा जानकारी के लिए, "ARMv8-A आर्किटेक्चर प्रोफ़ाइल के लिए, "ARM® आर्किटेक्चर रेफ़रंस मैन्युअल ARMv8" देखें.
http://infocenter.arm.com/help/topic/com.arm.doc.genc007826/Barrier_Litmus_Tests_and_Cookbook_A08.pdf - Linux कर्नेल मेमोरी बैरियर
- Linux कर्नेल मेमोरी बैरियर के लिए दस्तावेज़. इसमें कुछ काम के उदाहरण और ASCII आर्ट शामिल है.
http://www.kernel.org/doc/docs/memory-barriers.txt - ISO/IEC JTC1 SC22 WG21 (C++ मानक) 14882 (C++ प्रोग्रामिंग भाषा), सेक्शन 1.10 और क्लॉज़ 29 (“ऐटॉमिक ऑपरेशन लाइब्रेरी”)
- C++ ऐटॉमिक ऑपरेशन की सुविधाओं के लिए ड्राफ़्ट स्टैंडर्ड. यह वर्शन है
C+14 मानक के मुताबिक है, जिसमें इस क्षेत्र में कुछ मामूली बदलाव किए गए हैं
C+11 से लें.
http://www.open-std.org/jtc1/sc22/wg21/docs/Papers/2015/n4527.pdf
(परिचय: http://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf) - ISO/IEC JTC1 SC22 WG14 (सी मानक) 9899 (C प्रोग्रामिंग भाषा) चैप्टर 7.16 (“ऐटॉमिक्स <stdatomic.h>”)
- ISO/IEC 9899-201x C एटॉमिक ऑपरेशन की सुविधाओं के लिए ड्राफ़्ट स्टैंडर्ड.
ज़्यादा जानकारी के लिए, बाद में खराबी की रिपोर्ट देखें.
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf - प्रोसेसर के लिए C/C+11 मैपिंग (कैंब्रिज यूनिवर्सिटी)
- जेरोस्लाव सेवचिक और पीटर सेवेल का अनुवाद का संग्रह
जिसमें C++ ऐटॉमिक्स भी शामिल हैं.
http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html - Dekker का एल्गोरिदम
- “एक साथ काम करने वाले प्रोग्रामिंग में, म्यूचुअली एक्सक्लूज़न की समस्या का पहला सही हल”. Wikipedia के लेख में पूरा एल्गोरिदम दिया गया है. साथ ही, इसमें इस बात की चर्चा की गई है कि मॉडर्न ऑप्टिमाइज़ेशन कंपाइलर और एसएमपी हार्डवेयर के साथ काम करने के लिए, इसे कैसे अपडेट करना होगा.
https://en.wikipedia.org/wiki/Dekker's_algorithm - ARM बनाम ऐल्फ़ा वर्शन और पता डिपेंडेंसी पर टिप्पणियां
- कैटलिन मैरीना से आर्म-कर्नेल ईमेल पाने वाले लोगों की सूची में एक ईमेल. इसमें पते की खास जानकारी और कंट्रोल डिपेंडेंसी शामिल होती हैं.
http://linux.derkeiler.com/Mailing-Lists/Kernel/2009-05/msg11811.html - प्रत्येक प्रोग्रामर को मेमोरी के बारे में क्या पता होना चाहिए
- उलरिच ड्रेपर की ओर से, अलग-अलग तरह की मेमोरी, खास तौर पर सीपीयू कैश के बारे में एक बहुत बड़ा और विस्तार से लेख.
http://www.akkadia.org/drapper/cpumemory.pdf - ARM के कमज़ोर एक जैसे मेमोरी मॉडल के बारे में तर्क
- इस पेपर को चोंग और ने लिखा था ARM, Ltd. का इश्तियाक. यह ARM एसएमपी के मेमोरी मॉडल को सख्ती से, लेकिन आसानी से ऐक्सेस करने लायक बनाने की कोशिश करता है. यहां इस्तेमाल की गई “निगरानी” की परिभाषा इस पेपर से ली गई है. एक बार फिर, यह ARMv8 से पहले का है.
http://portal.acm.org/ft_gateway.cfm?id=1353528&type=pdf&coll\rdl लगतीCFID=96099715&CFTOKEN=57505711 - कंपाइलर राइटर्स के लिए JSR-133 कुकबुक
- डग ली ने इसे जेएसआर-133 (जावा मेमोरी मॉडल) दस्तावेज़ के साथी के तौर पर लिखा था. इसमें, लागू करने के दिशा-निर्देशों का शुरुआती सेट शामिल है
का इस्तेमाल किया जाता है, जिसका इस्तेमाल कई कंपाइलर लेखकों ने किया था. साथ ही,
अब भी बड़े पैमाने पर उद्धरण देते हैं और जिनके ज़रिए अहम जानकारी दी जा सकती है.
दुर्भाग्य से, बाड़ के जिन चार प्रकारों के बारे में यहां बताया गया है, वे अच्छे नहीं हैं
Android के साथ काम करने वाले आर्किटेक्चर और ऊपर दी गई C++11 मैपिंग के हिसाब से मैच करें
अब जावा के लिए भी, सटीक रेसिपी का बेहतर स्रोत हैं.
http://g.oswego.edu/dl/jmm/cookbook.html - x86-TSO: x86 मल्टीप्रोसेसर के लिए एक सख्त और उपयोग किया जा सकने वाला प्रोग्रामर का मॉडल
- x86 मेमोरी मॉडल की सटीक जानकारी. इसका सटीक ब्यौरा
माफ़ करें, ARM मेमोरी मॉडल काफ़ी जटिल हैं.
http://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf