जब मैं 3 डी ग्राफिक्स और गेम इंजन विकास के लिए एक ऑनलाइन डाउनलोड करने योग्य वीडियो ट्यूटोरियल के माध्यम से काम कर रहा था, आधुनिक ओपनजीएल के साथ काम कर रहा था। हमने volatile
अपनी कक्षाओं में से एक का उपयोग किया । ट्यूटोरियल वेबसाइट यहां पाई जा सकती है और volatile
कीवर्ड के साथ काम करने वाला Shader Engine
वीडियो श्रृंखला वीडियो 98 में पाया जाता है । ये कार्य मेरे स्वयं के नहीं हैं, लेकिन इसके लिए मान्यता प्राप्त हैं Marek A. Krzeminski, MASc
और यह वीडियो डाउनलोड पृष्ठ का एक अंश है।
और अगर आप उसकी वेबसाइट के लिए सब्सक्राइब किए गए हैं और इस वीडियो के भीतर उसकी वीडियो तक पहुंच है, तो वह प्रोग्रामिंग के साथ इस लेख का संदर्भ देता है।Volatile
multithreading
वाष्पशील: द मल्टीथ्रेडेड प्रोग्रामर बेस्ट फ्रेंड
आंद्रेई अलेक्जेंड्रेस्कु द्वारा, 01 फरवरी, 2001
वाष्पशील कीवर्ड संकलक अनुकूलन को रोकने के लिए तैयार किया गया था, जो कुछ अतुल्यकालिक घटनाओं की उपस्थिति में कोड गलत प्रस्तुत कर सकता है।
मैं आपका मूड खराब नहीं करना चाहता, लेकिन यह कॉलम मल्टीथ्रेडेड प्रोग्रामिंग के खतरनाक विषय को संबोधित करता है। यदि - जैसा कि जेनेरिक की पिछली किस्त कहती है - अपवाद-सुरक्षित प्रोग्रामिंग कठिन है, यह मल्टीथ्रेडेड प्रोग्रामिंग की तुलना में बच्चे का खेल है।
कई थ्रेड्स का उपयोग करने वाले प्रोग्राम सामान्य रूप से लिखने, सही साबित करने, डिबग करने, बनाए रखने और सामान्य रूप से वश में करने के लिए कुख्यात हैं। गलत मल्टीथ्रेडेड प्रोग्राम एक गड़बड़ के बिना वर्षों तक चल सकते हैं, केवल अप्रत्याशित रूप से चलाने के लिए क्योंकि कुछ महत्वपूर्ण समय की शर्त को पूरा किया गया है।
कहने की जरूरत नहीं है, एक प्रोग्रामर को मल्टीथ्रेडेड कोड लिखने में उसे मिलने वाली सभी मदद की जरूरत होती है। यह कॉलम दौड़ की स्थितियों पर ध्यान केंद्रित करता है - मल्टीथ्रेडेड कार्यक्रमों में परेशानी का एक सामान्य स्रोत - और आपको अंतर्दृष्टि और उपकरण प्रदान करता है कि उनसे कैसे बचें और, आश्चर्यजनक रूप से, संकलक ने आपकी मदद करने में कड़ी मेहनत की है।
बस एक छोटा सा खोजशब्द
यद्यपि थ्रेड्स की बात आती है, तो C और C ++ मानक दोनों स्पष्ट रूप से चुप हैं, वे अस्थिर कीवर्ड के रूप में मल्टीथ्रेडिंग के लिए थोड़ी रियायत करते हैं।
अपने बेहतर-ज्ञात समकक्षों की तरह, वाष्पशील एक प्रकार का संशोधक है। यह विभिन्न थ्रेड्स में एक्सेस किए गए और संशोधित किए गए चर के साथ संयोजन में उपयोग करने का इरादा है। मूल रूप से, अस्थिर के बिना, या तो मल्टीथ्रेडेड प्रोग्राम लिखना असंभव हो जाता है, या कंपाइलर विशाल अनुकूलन अवसरों को बर्बाद कर देता है। एक स्पष्टीकरण क्रम में है।
निम्नलिखित कोड पर विचार करें:
class Gadget {
public:
void Wait() {
while (!flag_) {
Sleep(1000);
}
}
void Wakeup() {
flag_ = true;
}
...
private:
bool flag_;
};
गैजेट का उद्देश्य :: ऊपर प्रतीक्षा करें हर सेकंड ध्वज_ सदस्य चर की जाँच करें और उस चर को दूसरे धागे द्वारा सही करने के लिए सेट किया गया है। कम से कम इसके प्रोग्रामर का इरादा है, लेकिन, अफसोस, प्रतीक्षा गलत है।
मान लीजिए कि संकलक आंकड़े बताते हैं कि स्लीप (1000) एक बाहरी पुस्तकालय में एक कॉल है जो संभवतः सदस्य चर flag_ को संशोधित नहीं कर सकता है। तब संकलक निष्कर्ष निकालता है कि यह फ्लैग_ को एक रजिस्टर में कैश कर सकता है और धीमी ऑन-बोर्ड मेमोरी तक पहुंचने के बजाय उस रजिस्टर का उपयोग कर सकता है। यह एकल-थ्रेडेड कोड के लिए एक उत्कृष्ट अनुकूलन है, लेकिन इस मामले में, यह शुद्धता को हानि पहुँचाता है: आप किसी गैजेट ऑब्जेक्ट के लिए प्रतीक्षा करने के बाद कॉल करते हैं, हालांकि एक अन्य थ्रेड को वेकअप कहते हैं, प्रतीक्षा हमेशा के लिए लूप करेगी। ऐसा इसलिए है क्योंकि फ्लैग_ का परिवर्तन उस रजिस्टर में परिलक्षित नहीं होगा जो फ्लैग_ को कैश करता है। अनुकूलन भी ... आशावादी है।
रजिस्टरों में कैशिंग चर बहुत मूल्यवान अनुकूलन है जो अधिकांश समय लागू होता है, इसलिए इसे बर्बाद करने के लिए एक दया होगी। C और C ++ आपको ऐसे कैशिंग को स्पष्ट रूप से अक्षम करने का मौका देता है। यदि आप किसी चर पर वाष्पशील संशोधक का उपयोग करते हैं, तो संकलक उस चर को रजिस्टर में कैश नहीं करेगा - प्रत्येक पहुंच उस चर की वास्तविक मेमोरी लोकेशन को हिट करेगा। तो आपको केवल गैजेट के इंतजार / वेकअप कॉम्बो काम करने के लिए करना होगा जो कि फ्लैग_ को उचित रूप से अर्हता प्राप्त करने के लिए है:
class Gadget {
public:
... as above ...
private:
volatile bool flag_;
};
वाष्पीकरण के औचित्य और उपयोग के अधिकांश स्पष्टीकरण यहां बंद हो जाते हैं और आपको बहु-सूत्र में उपयोग किए जाने वाले आदिम प्रकारों को अस्थिर करने की सलाह देते हैं। हालाँकि, बहुत कुछ है जो आप अस्थिरता के साथ कर सकते हैं, क्योंकि यह C ++ की अद्भुत प्रकार प्रणाली का हिस्सा है।
उपयोगकर्ता-परिभाषित प्रकारों के साथ अस्थिरता का उपयोग करना
आप न केवल आदिम प्रकार, बल्कि उपयोगकर्ता-परिभाषित प्रकार भी अस्थिर-योग्य हो सकते हैं। उस स्थिति में, वाष्पशील एक प्रकार से कास्ट के समान होता है। (आप एक साथ एक ही प्रकार पर कांस्टेबल और वाष्पशील भी लागू कर सकते हैं।)
कास्ट के विपरीत, आदिम प्रकार और उपयोगकर्ता-परिभाषित प्रकारों के बीच अस्थिर भेदभाव होता है। सामान्य रूप से, कक्षाओं के विपरीत, आदिम प्रकार अभी भी अपने सभी संचालन (इसके अलावा, गुणा, असाइनमेंट, आदि) का समर्थन करते हैं जब अस्थिर-योग्य होते हैं। उदाहरण के लिए, आप एक गैर-वाष्पशील int को एक वाष्पशील int को असाइन कर सकते हैं, लेकिन आप एक गैर-वाष्पशील वस्तु को एक वाष्पशील वस्तु को असाइन नहीं कर सकते।
आइए उदाहरण के आधार पर उपयोगकर्ता-परिभाषित प्रकारों पर वाष्पशील कैसे काम करता है, इसका उदाहरण दें।
class Gadget {
public:
void Foo() volatile;
void Bar();
...
private:
String name_;
int state_;
};
...
Gadget regularGadget;
volatile Gadget volatileGadget;
यदि आपको लगता है कि अस्थिर वस्तुओं के साथ उपयोगी नहीं है, तो कुछ आश्चर्य के लिए तैयार करें।
volatileGadget.Foo();
regularGadget.Foo();
volatileGadget.Bar();
गैर-योग्य प्रकार से इसके वाष्पशील प्रतिरूप में रूपांतरण तुच्छ है। हालाँकि, कास्ट के साथ के रूप में, आप यात्रा को अस्थिर से गैर-योग्य तक वापस नहीं कर सकते। आपको एक कास्ट का उपयोग करना चाहिए:
Gadget& ref = const_cast<Gadget&>(volatileGadget);
ref.Bar();
एक अस्थिर-योग्य वर्ग केवल अपने इंटरफ़ेस के सबसेट तक पहुँच देता है, एक उपसमूह जो वर्ग कार्यान्वयनकर्ता के नियंत्रण में है। उपयोगकर्ता केवल एक const_cast का उपयोग करके उस प्रकार के इंटरफ़ेस तक पूर्ण पहुंच प्राप्त कर सकते हैं। इसके अलावा, कब्ज की तरह, अस्थिरता कक्षा से अपने सदस्यों तक फैलती है (उदाहरण के लिए, volatileGadget.name_ और volatileGadget.state_ अस्थिर संस्करण हैं)।
अस्थिर, क्रिटिकल सेक्शन और रेस की स्थिति
मल्टीथ्रेडेड प्रोग्रामों में सबसे सरल और सबसे अधिक इस्तेमाल किया जाने वाला सिंक्रनाइज़ेशन डिवाइस म्यूटेक्स है। एक म्यूटेक्स एक्वायर और रिलीज प्राइमेटिक्स को उजागर करता है। एक बार जब आप किसी थ्रेड में एक्वायर को कॉल करते हैं, तो एक्वायर को कॉल करने वाला कोई अन्य थ्रेड ब्लॉक हो जाएगा। बाद में, जब वह थ्रेड कॉल रिलीज़ होता है, तो एक एक्वायर कॉल में अवरुद्ध एक थ्रेड रिलीज़ किया जाएगा। दूसरे शब्दों में, किसी दिए गए म्यूटेक्स के लिए, केवल एक थ्रेड को प्रोसेसर को एक्वायर करने के लिए और रिलीज के लिए कॉल के बीच में समय मिल सकता है। एक्वायर्ड को कॉल करने और रिलीज़ करने के लिए कॉल के बीच निष्पादन कोड को एक महत्वपूर्ण अनुभाग कहा जाता है। (विंडोज़ शब्दावली थोड़ी भ्रामक है क्योंकि यह म्यूटेक्स को एक महत्वपूर्ण खंड कहता है, जबकि "म्यूटेक्स" वास्तव में एक इंटर-प्रोसेस म्यूटेक्स है। अच्छा होता यदि उन्हें थ्रेड म्यूटेक्स और प्रोसेस म्यूटेक्स कहा जाता।
म्यूटेक्स का उपयोग दौड़ की स्थितियों के खिलाफ डेटा की सुरक्षा के लिए किया जाता है। परिभाषा के अनुसार, एक दौड़ की स्थिति तब होती है जब डेटा पर अधिक थ्रेड्स का प्रभाव निर्भर करता है कि थ्रेड्स कैसे शेड्यूल किए गए हैं। रेस की स्थितियां तब दिखाई देती हैं जब दो या अधिक धागे एक ही डेटा का उपयोग करने के लिए प्रतिस्पर्धा करते हैं। क्योंकि थ्रेड्स समय में एक-दूसरे को मनमाने तरीके से बाधित कर सकते हैं, डेटा दूषित या गलत हो सकता है। नतीजतन, परिवर्तन और कभी-कभी डेटा तक पहुंच को महत्वपूर्ण वर्गों के साथ सावधानीपूर्वक संरक्षित किया जाना चाहिए। ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग में, इसका आम तौर पर अर्थ है कि आप एक वर्ग में एक म्यूटेक्स को एक सदस्य चर के रूप में संग्रहीत करते हैं और जब भी आप उस कक्षा तक पहुंचते हैं, तब इसका उपयोग करते हैं।
अनुभवी मल्टीथ्रेडेड प्रोग्रामर ने ऊपर दिए गए दो पैराग्राफ को पढ़ते हुए जम्हाई ली होगी, लेकिन उनका उद्देश्य एक बौद्धिक कसरत प्रदान करना है, क्योंकि अब हम अस्थिर कनेक्शन के साथ जुड़ेंगे। हम इसे C ++ प्रकार की दुनिया और थ्रेडिंग शब्दार्थ दुनिया के बीच एक समानांतर ड्राइंग द्वारा करते हैं।
- एक महत्वपूर्ण खंड के बाहर, कोई भी धागा किसी भी समय किसी अन्य को बाधित कर सकता है; कोई नियंत्रण नहीं है, इसलिए परिणामस्वरूप कई थ्रेड से सुलभ चर अस्थिर हैं। यह वाष्पशील के मूल इरादे को ध्यान में रखते हुए है - जो कि संकलक को एक बार में कई थ्रेड द्वारा उपयोग किए जाने वाले अनजाने कैशिंग मूल्यों से रोकना है।
- म्यूटेक्स द्वारा परिभाषित एक महत्वपूर्ण खंड के अंदर, केवल एक धागे तक पहुंच है। नतीजतन, एक महत्वपूर्ण खंड के अंदर, निष्पादन कोड में एकल-थ्रेडेड शब्दार्थ है। नियंत्रित चर अब अस्थिर नहीं है - आप अस्थिर गुणक को हटा सकते हैं।
संक्षेप में, थ्रेड्स के बीच साझा किया गया डेटा एक महत्वपूर्ण अनुभाग के बाहर वैचारिक रूप से अस्थिर है, और एक महत्वपूर्ण अनुभाग के अंदर गैर-अस्थिर है।
आप म्यूटेक्स को लॉक करके एक महत्वपूर्ण अनुभाग दर्ज करते हैं। आप const_cast को लागू करके एक प्रकार से वाष्पशील क्वालिफायर निकालते हैं। यदि हम इन दोनों ऑपरेशनों को एक साथ करने का प्रबंधन करते हैं, तो हम C ++ के टाइप सिस्टम और एप्लिकेशन के थ्रेडिंग शब्दार्थों के बीच संबंध बनाते हैं। हम अपने लिए कंपाइलर चेक रेस की स्थिति बना सकते हैं।
LockingPtr
हमें एक उपकरण की आवश्यकता है जो एक म्यूटेक्स अधिग्रहण और एक const_cast एकत्र करता है। चलिए एक LockingPtr क्लास टेम्पलेट विकसित करते हैं, जिसे आप एक वाष्पशील वस्तु obj और एक म्यूटेक्स mtx के साथ आरंभ करते हैं। अपने जीवनकाल के दौरान, एक LockingPtr mtx का अधिग्रहण करता है। इसके अलावा, LockingPtr अस्थिर-पट्टी वाले obj तक पहुंच प्रदान करता है। एक्सेस को एक स्मार्ट पॉइंटर फैशन में, ऑपरेटर-> और ऑपरेटर * के माध्यम से पेश किया जाता है। Const_cast LockingPtr के अंदर किया जाता है। कास्ट शब्दबद्ध रूप से मान्य है क्योंकि लॉकिंगप्रट अपने जीवनकाल के लिए म्यूटेक्स का अधिग्रहण करता है।
सबसे पहले, एक वर्ग Mutex के कंकाल को परिभाषित करें जिसके साथ LockingPtr काम करेगा:
class Mutex {
public:
void Acquire();
void Release();
...
};
LockingPtr का उपयोग करने के लिए, आप अपने ऑपरेटिंग सिस्टम के मूल डेटा संरचनाओं और आदिम कार्यों का उपयोग करके म्यूटेक्स को लागू करते हैं।
LockingPtr को नियंत्रित चर के प्रकार के साथ जोड़ दिया जाता है। उदाहरण के लिए, यदि आप किसी विजेट को नियंत्रित करना चाहते हैं, तो आप लॉकिंगप्रट का उपयोग करते हैं जिसे आप टाइप वाष्पशील विजेट के एक चर के साथ आरंभ करते हैं।
LockingPtr की परिभाषा बहुत सरल है। LockingPtr एक अपरिष्कृत स्मार्ट पॉइंटर को लागू करता है। यह केवल एक const_cast और एक महत्वपूर्ण अनुभाग एकत्र करने पर केंद्रित है।
template <typename T>
class LockingPtr {
public:
LockingPtr(volatile T& obj, Mutex& mtx)
: pObj_(const_cast<T*>(&obj)), pMtx_(&mtx) {
mtx.Lock();
}
~LockingPtr() {
pMtx_->Unlock();
}
T& operator*() {
return *pObj_;
}
T* operator->() {
return pObj_;
}
private:
T* pObj_;
Mutex* pMtx_;
LockingPtr(const LockingPtr&);
LockingPtr& operator=(const LockingPtr&);
};
अपनी सरलता के बावजूद, लॉकिंगप्रट्रेट सही मल्टीथ्रेडेड कोड लिखने में एक बहुत ही उपयोगी सहायता है। आपको उन वस्तुओं को परिभाषित करना चाहिए जो धागे के बीच अस्थिर हैं और उनके साथ कभी भी const_cast का उपयोग नहीं करते हैं - हमेशा लॉकिंगपार्ट स्वचालित ऑब्जेक्ट का उपयोग करें। आइए एक उदाहरण के साथ इसका उदाहरण दें।
मान लें कि आपके पास दो धागे हैं जो एक वेक्टर वस्तु साझा करते हैं:
class SyncBuf {
public:
void Thread1();
void Thread2();
private:
typedef vector<char> BufT;
volatile BufT buffer_;
Mutex mtx_;
};
एक थ्रेड फ़ंक्शन के अंदर, आप बफर_ सदस्य चर पर नियंत्रित पहुंच प्राप्त करने के लिए लॉकिंगप्रट का उपयोग करते हैं:
void SyncBuf::Thread1() {
LockingPtr<BufT> lpBuf(buffer_, mtx_);
BufT::iterator i = lpBuf->begin();
for (; i != lpBuf->end(); ++i) {
... use *i ...
}
}
कोड लिखना और समझना बहुत आसान है - जब भी आपको बफर_ का उपयोग करने की आवश्यकता होती है, तो आपको इसे इंगित करते हुए एक लॉकिंगप्राट बनाना होगा। एक बार जब आप ऐसा करते हैं, तो आपके पास वेक्टर के संपूर्ण इंटरफ़ेस तक पहुंच होती है।
अच्छा हिस्सा यह है कि यदि आप कोई गलती करते हैं, तो संकलक इसे इंगित करेगा:
void SyncBuf::Thread2() {
BufT::iterator i = buffer_.begin();
for ( ; i != lpBuf->end(); ++i ) {
... use *i ...
}
}
जब तक आप या तो एक const_cast लागू नहीं करते हैं या LockingPtr का उपयोग करते हैं, आप बफर_ के किसी भी फ़ंक्शन तक नहीं पहुंच सकते हैं। अंतर यह है कि LockingPtr, अस्थिर चर के लिए const_cast लागू करने का आदेश दिया तरीका प्रदान करता है।
LockPPtr उल्लेखनीय रूप से अभिव्यंजक है। यदि आपको केवल एक फ़ंक्शन को कॉल करने की आवश्यकता है, तो आप एक अनाम अस्थायी LockingPtr ऑब्जेक्ट बना सकते हैं और इसे सीधे उपयोग कर सकते हैं:
unsigned int SyncBuf::Size() {
return LockingPtr<BufT>(buffer_, mtx_)->size();
}
वापस आदिम प्रकार के लिए
हमने देखा कि कैसे निर्बाध रूप से अनियंत्रित पहुंच के खिलाफ वस्तुओं की सुरक्षा करता है और लॉकिंगपार्ट धागा-सुरक्षित कोड लिखने का एक सरल और प्रभावी तरीका प्रदान करता है। आइए अब आदिम प्रकारों पर लौटते हैं, जिनका व्यवहार भिन्न-भिन्न प्रकार से किया जाता है।
आइए एक उदाहरण पर विचार करें जहां कई थ्रेड्स प्रकार के चर को साझा करते हैं int।
class Counter {
public:
...
void Increment() { ++ctr_; }
void Decrement() { —ctr_; }
private:
int ctr_;
};
अगर वृद्धि और विकृति को विभिन्न थ्रेड्स से बुलाया जाना है, तो ऊपर का टुकड़ा छोटी गाड़ी है। सबसे पहले, ctr_ को अस्थिर होना चाहिए। दूसरा, यहां तक कि एक प्रतीत होता है परमाणु ऑपरेशन जैसे कि ++ ctr_ वास्तव में एक तीन चरण का ऑपरेशन है। मेमोरी में स्वयं कोई अंकगणित क्षमताएं नहीं हैं। जब एक चर बढ़ाना, प्रोसेसर:
- उस चर को एक रजिस्टर में पढ़ता है
- रजिस्टर में मूल्य बढ़ाता है
- परिणाम को वापस स्मृति में लिखता है
इस तीन-चरण ऑपरेशन को आरएमडब्ल्यू (रीड-मॉडिफाई-राइट) कहा जाता है। आरएमडब्ल्यू ऑपरेशन के संशोधित हिस्से के दौरान, अधिकांश प्रोसेसर मेमोरी बस को मुफ्त देते हैं ताकि अन्य प्रोसेसर को मेमोरी तक पहुंच प्रदान की जा सके।
यदि उस समय एक और प्रोसेसर एक ही चर पर आरएमडब्ल्यू ऑपरेशन करता है, तो हमारे पास एक दौड़ की स्थिति है: दूसरा लेखन पहले के प्रभाव को ओवरराइट करता है।
उससे बचने के लिए, आप फिर से, LockingPtr पर भरोसा कर सकते हैं:
class Counter {
public:
...
void Increment() { ++*LockingPtr<int>(ctr_, mtx_); }
void Decrement() { —*LockingPtr<int>(ctr_, mtx_); }
private:
volatile int ctr_;
Mutex mtx_;
};
अब कोड सही है, लेकिन SyncBuf के कोड की तुलना में इसकी गुणवत्ता हीन है। क्यों? क्योंकि काउंटर के साथ, कंपाइलर आपको चेतावनी नहीं देगा यदि आप गलती से ctr_ को सीधे एक्सेस कर रहे हैं (इसे लॉक किए बिना)। संकलक ++ ctr_ अगर ctr_ अस्थिर है, तो उत्पन्न कोड बस गलत है। संकलक अब आपका सहयोगी नहीं है, और केवल आपका ध्यान आपको दौड़ की स्थिति से बचने में मदद कर सकता है।
तब आपको क्या करना चाहिए? बस उन प्राइमिटिव डेटा को एनकैप्सुलेट करें जो आप उच्च-स्तरीय संरचनाओं में उपयोग करते हैं और उन संरचनाओं के साथ अस्थिरता का उपयोग करते हैं। विरोधाभासी रूप से, यह अंतर्निहित के साथ सीधे अस्थिर का उपयोग करने के लिए बदतर है, इस तथ्य के बावजूद कि शुरू में यह अस्थिर का उपयोग इरादा था!
अस्थिर सदस्य कार्य
अब तक, हमारे पास ऐसी कक्षाएं हैं जो अस्थिर डेटा सदस्यों को एकत्रित करती हैं; अब चलो कक्षाओं को डिजाइन करने के बारे में सोचते हैं जो बदले में बड़ी वस्तुओं का हिस्सा होंगे और थ्रेड्स के बीच साझा किए जाएंगे। यहां वह जगह है जहां अस्थिर सदस्य कार्य बहुत मदद कर सकते हैं।
अपनी कक्षा को डिजाइन करते समय, आप केवल उन सदस्य कार्यों को अस्थिर करते हैं जो थ्रेड सुरक्षित हैं। आपको यह मान लेना चाहिए कि बाहर से कोड किसी भी समय किसी भी कोड से अस्थिर कार्यों को कॉल करेगा। मत भूलो: वाष्पशील मुक्त मल्टीथ्रेडेड कोड और कोई महत्वपूर्ण खंड के बराबर है; गैर-वाष्पशील एकल-थ्रेडेड परिदृश्य या एक महत्वपूर्ण अनुभाग के अंदर बराबर होता है।
उदाहरण के लिए, आप एक वर्ग विजेट को परिभाषित करते हैं जो एक ऑपरेशन को दो वेरिएंट में लागू करता है - एक थ्रेड-सेफ एक और एक तेज़, असुरक्षित एक।
class Widget {
public:
void Operation() volatile;
void Operation();
...
private:
Mutex mtx_;
};
ओवरलोडिंग के उपयोग पर ध्यान दें। अब विजेट का उपयोगकर्ता अस्थिर वस्तुओं के लिए एक समान सिंटैक्स का उपयोग करके ऑपरेशन को आमंत्रित कर सकता है और थ्रेड सुरक्षा प्राप्त कर सकता है, या नियमित वस्तुओं के लिए और गति प्राप्त कर सकता है। उपयोगकर्ता को साझा विजेट ऑब्जेक्ट को अस्थिर के रूप में परिभाषित करने के बारे में सावधान रहना चाहिए।
एक अस्थिर सदस्य फ़ंक्शन को लागू करते समय, पहला ऑपरेशन आमतौर पर इसे लॉकिंगपार्ट के साथ लॉक करना होता है। तब काम गैर-अस्थिर सिबलिंग का उपयोग करके किया जाता है:
void Widget::Operation() volatile {
LockingPtr<Widget> lpThis(*this, mtx_);
lpThis->Operation();
}
सारांश
जब मल्टीथ्रेडेड प्रोग्राम लिखते हैं, तो आप अपने लाभ के लिए अस्थिर का उपयोग कर सकते हैं। आपको निम्नलिखित नियमों पर चलना चाहिए:
- सभी साझा किए गए ऑब्जेक्ट को अस्थिर के रूप में परिभाषित करें।
- आदिम प्रकारों के साथ सीधे वाष्पशील का उपयोग न करें।
- साझा कक्षाओं को परिभाषित करते समय, धागा सुरक्षा को व्यक्त करने के लिए अस्थिर सदस्य कार्यों का उपयोग करें।
यदि आप ऐसा करते हैं, और यदि आप साधारण जेनेरिक कंपोनेंट LockingPtr का उपयोग करते हैं, तो आप थ्रेड-सेफ कोड लिख सकते हैं और दौड़ की स्थिति के बारे में बहुत कम चिंता कर सकते हैं, क्योंकि कंपाइलर आपके लिए चिंता करेगा और उन स्थानों पर परिश्रम से इंगित करेगा जहां आप गलत हैं।
कुछ परियोजनाओं को मैंने बड़े प्रभाव से अस्थिर और लॉकिंगप्रेट के उपयोग के साथ जोड़ा है। कोड साफ और समझ में आता है। मैं कुछ गतिरोधों को याद करता हूं, लेकिन मैं दौड़ की स्थिति के लिए गतिरोध पसंद करता हूं क्योंकि वे डिबग करना बहुत आसान हैं। वस्तुतः नस्ल की स्थितियों से संबंधित कोई समस्या नहीं थी। लेकिन तब आपको कभी पता नहीं चलता।
स्वीकृतियाँ
जेम्स कान्जे और सोरिन जियानू के लिए बहुत धन्यवाद जिन्होंने व्यावहारिक विचारों के साथ मदद की।
आंद्रेई अलेक्जेंड्रेस्कु रियलनेटवर्क इंक (www.realnetworks.com) में एक विकास प्रबंधक है, जो सिएटल, वाशिंगटन में स्थित है, और प्रशंसित पुस्तक मॉडर्न सी ++ डिजाइन के लेखक हैं। उनसे www.moderncppdesign.com पर संपर्क किया जा सकता है। आंद्रेई भी C ++ सेमिनार (www.gotw.ca/cpp_seminar) के चुनिंदा प्रशिक्षकों में से एक हैं।
यह लेख थोड़ा दिनांकित हो सकता है, लेकिन यह हमारे लिए दौड़ की शर्तों के लिए संकलक जाँच करते समय घटनाओं को अतुल्यकालिक रखने में मदद करने के लिए मल्टीथ्रेडेड प्रोग्रामिंग के उपयोग के साथ वाष्पशील संशोधक के उपयोग के एक उत्कृष्ट उपयोग के लिए अच्छी जानकारी देता है। यह सीधे ओप्स के मूल प्रश्न का उत्तर नहीं दे सकता है मेमोरी बाड़ बनाने के बारे में, लेकिन मैं इसे दूसरों के लिए एक उत्तर के रूप में पोस्ट करने का चयन करता हूं, जब मल्टीथ्रेडेड एप्लिकेशन के साथ काम करते हुए अस्थिरता के अच्छे उपयोग के लिए एक उत्कृष्ट संदर्भ।