सबसे पहले, आपको एक भाषा वकील की तरह सोचना सीखना होगा।
C ++ विनिर्देशन किसी विशेष संकलक, ऑपरेटिंग सिस्टम, या CPU का संदर्भ नहीं देता है। यह एक अमूर्त मशीन का संदर्भ देता है जो वास्तविक प्रणालियों का एक सामान्यीकरण है। भाषा वकील की दुनिया में, प्रोग्रामर का काम अमूर्त मशीन के लिए कोड लिखना है; संकलक का काम उस कोड को एक ठोस मशीन पर साकार करना है। युक्ति से कठोरता से कोड करके, आप निश्चित हो सकते हैं कि आपका कोड किसी भी सिस्टम पर किसी भी सिस्टम पर संशोधन के बिना संकलित और चला जाएगा, चाहे वह आज से या आज से 50 साल पहले हो।
C ++ 98 / C ++ 03 विनिर्देशन में सार मशीन मौलिक रूप से एकल-थ्रेडेड है। इसलिए बहु-थ्रेडेड सी ++ कोड लिखना संभव नहीं है जो कि कल्पना के संबंध में "पूरी तरह से पोर्टेबल" है। कल्पना भी मेमोरी लोड और स्टोर की परमाणुता के बारे में कुछ भी नहीं कहती है या जिस क्रम में लोड और स्टोर हो सकता है, म्यूटेक्स जैसी चीजों का कभी भी ध्यान न रखें।
बेशक, आप विशेष रूप से कंक्रीट सिस्टम के लिए बहु-थ्रेडेड कोड लिख सकते हैं - जैसे कि पीथ्रेड या विंडोज। लेकिन C ++ 98 / C ++ 03 के लिए बहु-थ्रेडेड कोड लिखने का कोई मानक तरीका नहीं है ।
C ++ 11 में सार मशीन डिजाइन द्वारा बहु-थ्रेडेड है। इसमें एक अच्छी तरह से परिभाषित मेमोरी मॉडल भी है ; यह है, यह कहता है कि संकलक क्या कर सकता है और जब यह स्मृति तक पहुँचने की बात नहीं करता है।
निम्नलिखित उदाहरण पर विचार करें, जहां वैश्विक चर की एक जोड़ी को दो धागे से समवर्ती रूप से एक्सेस किया जाता है:
Global
int x, y;
Thread 1 Thread 2
x = 17; cout << y << " ";
y = 37; cout << x << endl;
थ्रेड 2 आउटपुट क्या हो सकता है?
C ++ 98 / C ++ 03 के तहत, यह अपरिभाषित व्यवहार भी नहीं है; प्रश्न ही निरर्थक है क्योंकि मानक किसी भी चीज को "थ्रेड" नहीं कहता है।
सी ++ 11 के तहत, परिणाम अपरिभाषित व्यवहार है, क्योंकि लोड और स्टोर को सामान्य रूप से परमाणु नहीं होना चाहिए। जो एक बहुत सुधार की तरह नहीं लग सकता है ... और अपने आप से, यह नहीं है।
लेकिन C ++ 11 के साथ, आप इसे लिख सकते हैं:
Global
atomic<int> x, y;
Thread 1 Thread 2
x.store(17); cout << y.load() << " ";
y.store(37); cout << x.load() << endl;
अब चीजें बहुत अधिक दिलचस्प हो गई हैं। सबसे पहले, यहां व्यवहार को परिभाषित किया गया है । थ्रेड 2 अब प्रिंट कर सकता है 0 0
(यदि यह थ्रेड 1 से पहले चलता है), 37 17
(यदि यह थ्रेड 1 के बाद चलता है), या 0 17
(यदि थ्रेड 1 के बाद चलता है तो x पर असाइन होता है, लेकिन इससे पहले कि यह y को असाइन करता है)।
यह क्या प्रिंट नहीं कर सकता है 37 0
, क्योंकि सी ++ 11 में परमाणु भार / स्टोर के लिए डिफ़ॉल्ट मोड अनुक्रमिक स्थिरता को लागू करना है । इसका मतलब यह है कि सभी लोड और स्टोर "होने चाहिए" जैसे कि वे उस क्रम में हुए थे जो आपने उन्हें प्रत्येक थ्रेड के भीतर लिखा था, जबकि थ्रेड के बीच संचालन को इंटरलेय किया जा सकता है, हालांकि सिस्टम पसंद करता है। अतः परमाणु का डिफ़ॉल्ट व्यवहार परमाणु और भार और भंडार दोनों के लिए आदेश प्रदान करता है।
अब, एक आधुनिक सीपीयू पर, क्रमिक स्थिरता सुनिश्चित करना महंगा हो सकता है। विशेष रूप से, कंपाइलर यहां हर पहुंच के बीच फुल-मेमोरी मेमोरी बाधाओं का उत्सर्जन करने की संभावना है। लेकिन अगर आपका एल्गोरिथ्म आउट-ऑफ-ऑर्डर लोड और स्टोर को सहन कर सकता है; यानी, अगर इसके लिए परमाणुता की आवश्यकता है, लेकिन आदेश देने की नहीं; अर्थात, यदि यह 37 0
इस प्रोग्राम से आउटपुट के रूप में सहन कर सकता है , तो आप इसे लिख सकते हैं:
Global
atomic<int> x, y;
Thread 1 Thread 2
x.store(17,memory_order_relaxed); cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed); cout << x.load(memory_order_relaxed) << endl;
सीपीयू जितना आधुनिक होगा, उतनी ही संभावना पिछले उदाहरण की तुलना में तेज होगी।
अंत में, यदि आपको केवल विशेष लोड और स्टोर रखने की आवश्यकता है, तो आप लिख सकते हैं:
Global
atomic<int> x, y;
Thread 1 Thread 2
x.store(17,memory_order_release); cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release); cout << x.load(memory_order_acquire) << endl;
यह हमें आदेशित भार और दुकानों में वापस ले जाता है - इसलिए 37 0
अब संभव उत्पादन नहीं है - लेकिन यह न्यूनतम ओवरहेड के साथ ऐसा करता है। (इस तुच्छ उदाहरण में, परिणाम पूर्ण विकसित अनुक्रमिक स्थिरता के समान है; एक बड़े कार्यक्रम में, यह नहीं होगा।)
बेशक, यदि केवल आउटपुट आप देखना चाहते हैं 0 0
या 37 17
, आप मूल कोड के चारों ओर म्यूटेक्स लपेट सकते हैं। लेकिन अगर आपने इसे पढ़ा है, तो मुझे यकीन है कि आप पहले से ही जानते हैं कि यह कैसे काम करता है, और यह उत्तर पहले से अधिक लंबा है जैसा कि मेरा इरादा है :-)।
तो, नीचे की रेखा। म्यूटेक्स महान हैं, और सी ++ 11 उन्हें मानकीकृत करता है। लेकिन कभी-कभी प्रदर्शन के कारणों से आप निचले स्तर के आदिम (उदाहरण के लिए, क्लासिक डबल-चेकिंग लॉकिंग पैटर्न ) चाहते हैं। नया मानक म्यूटेक्स और कंडीशन वैरिएबल्स जैसे उच्च-स्तरीय गैजेट प्रदान करता है, और यह निम्न-स्तरीय गैजेट जैसे परमाणु प्रकार और मेमोरी अवरोध के विभिन्न स्वाद भी प्रदान करता है। तो अब आप मानक द्वारा निर्दिष्ट भाषा के भीतर परिष्कृत, उच्च-प्रदर्शन समवर्ती दिनचर्या लिख सकते हैं, और आप निश्चित हो सकते हैं कि आपका कोड आज के सिस्टम और कल दोनों पर अपरिवर्तित होगा।
हालांकि फ्रैंक होने के लिए, जब तक आप एक विशेषज्ञ नहीं हैं और कुछ गंभीर निम्न-स्तर के कोड पर काम कर रहे हैं, तो आपको संभवतः म्यूटेक्स और कंडीशन चर पर चिपकना चाहिए। यही मेरा इरादा है।
इस सामान पर अधिक जानकारी के लिए, इस ब्लॉग पोस्ट को देखें ।