केवल इस उत्तर को जोड़ने के कारण मुझे लगता है कि स्वीकृत उत्तर भ्रामक हो सकता है। सभी मामलों में आपको अपने कोड को थ्रेड-सेफ़ होने के लिए कहीं न कहीं से अधिसूचित करने के लिए (_) कॉल करने से पहले म्यूटेक्स को लॉक करना होगा, हालाँकि आप वास्तव में सूचित _ * () को कॉल करने से पहले इसे फिर से अनलॉक कर सकते हैं।
स्पष्ट करने के लिए, आपको प्रतीक्षा (lk) दर्ज करने से पहले ताला लेना होगा क्योंकि प्रतीक्षा () अनलॉक lk है और यदि लॉक लॉक नहीं किया गया था तो यह अपरिभाषित व्यवहार होगा। यह सूचित_ओन () के साथ नहीं है, लेकिन आपको यह सुनिश्चित करने की आवश्यकता है कि आप प्रतीक्षा में प्रवेश करने से पहले आपको सूचित नहीं करेंगे _ * () और कॉल को म्यूटेक्स अनलॉक करने से पहले; जो स्पष्ट रूप से केवल सूचित करें * * () को कॉल करने से पहले उसी म्यूटेक्स को लॉक करके किया जा सकता है।
उदाहरण के लिए, निम्नलिखित मामले पर विचार करें:
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999)
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0)
return;
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
चेतावनी : इस कोड में एक बग है।
यह विचार निम्नलिखित है: थ्रेड्स कॉल स्टार्ट () और स्टॉप () जोड़े में, लेकिन केवल जब तक प्रारंभ () सही लौटे। उदाहरण के लिए:
if (start())
{
stop();
}
किसी बिंदु पर एक (अन्य) थ्रेड कैंसिल () को रद्द करेगा और कैंसल करने से लौटने के बाद (उन वस्तुओं को नष्ट कर देगा जिनकी 'डू स्टफ' में जरूरत है)। हालाँकि, रद्द () माना जाता है कि वापसी नहीं होती है जबकि स्टार्ट () और स्टॉप () के बीच धागे होते हैं, और एक बार रद्द () अपनी पहली पंक्ति को निष्पादित कर दिया जाता है, तो प्रारंभ () हमेशा गलत वापस आ जाएगा, इसलिए कोई नया थ्रेड 'डू' में प्रवेश नहीं करेगा। सामान 'क्षेत्र।
सही काम करता है?
तर्क इस प्रकार है:
1) यदि कोई भी धागा प्रारंभ की पहली पंक्ति को सफलतापूर्वक निष्पादित करता है () और (इसलिए सच लौटेगा) तो किसी भी धागे ने रद्द करने की पहली पंक्ति को निष्पादित नहीं किया () अभी तक (हम मानते हैं कि धागे की कुल संख्या 1000 से बहुत कम है। मार्ग)।
2) इसके अलावा, जबकि एक थ्रेड ने प्रारंभ की पहली पंक्ति को सफलतापूर्वक निष्पादित किया (), लेकिन अभी तक स्टॉप की पहली पंक्ति () नहीं है, तो यह असंभव है कि कोई भी धागा सफलतापूर्वक रद्द की पहली पंक्ति () को निष्पादित करेगा (ध्यान दें कि केवल एक धागा कभी कॉल रद्द ()): fetch_sub (1000) द्वारा दिया गया मान 0 से बड़ा होगा।
3) एक बार एक धागे को रद्द करने की पहली पंक्ति को निष्पादित करने के बाद (), शुरुआत की पहली पंक्ति () हमेशा झूठी आएगी और एक थ्रेड कॉलिंग स्टार्ट () अब 'डू सामान' क्षेत्र में प्रवेश नहीं करेगी।
4) शुरू करने के लिए कॉल की संख्या () और स्टॉप () हमेशा संतुलित होती है, इसलिए रद्द करने की पहली पंक्ति के बाद () असफल रूप से निष्पादित की जाती है, हमेशा एक पल होगा जहां एक (अंतिम) कॉल को रोकने () का कारण बनता है -1000 तक पहुँचने के लिए और इसलिए inform_one () कहा जाता है। ध्यान दें कि कभी भी केवल तभी हो सकता है जब रद्द करने की पहली पंक्ति उस धागे के माध्यम से गिरती है।
एक भुखमरी की समस्या के अलावा जहां इतने सारे थ्रेड्स स्टार्ट () / स्टॉप () हैं जो गिनती -1000 तक नहीं पहुंचते हैं और रद्द () कभी नहीं लौटते हैं, जिसे कोई "संभावना नहीं के रूप में स्वीकार कर सकता है और लंबे समय तक नहीं रह सकता है", एक और बग है:
यह संभव है कि 'डू स्टफ' क्षेत्र के अंदर एक धागा है, जो कहता है कि यह बस स्टॉप कह रहा है (); उस क्षण में एक थ्रेड रद्द करने की पहली पंक्ति को निष्पादित करता है () fetch_sub (1000) के साथ मान 1 पढ़ रहा है और गिर रहा है। लेकिन इससे पहले कि यह म्यूटेक्स और / या प्रतीक्षा करने के लिए कॉल करता है (lk), पहला धागा स्टॉप की पहली पंक्ति को निष्पादित करता है (), पढ़ता -999 और कॉल cv.notify_one ()!
फिर इस कॉल को Inform_one () में किया जाता है, इससे पहले कि हम इंतज़ार कर रहे हैं () - कंडीशन वेरिएबल पर! और कार्यक्रम अनिश्चित काल के लिए बंद हो जाएगा।
इस कारण से हमें सूचित नहीं किया जा सकता है जब तक कि हम इंतजार नहीं करते हैं। ध्यान दें कि एक कंडीशन वैरिएबल की शक्ति इसमें निहित है कि यह एटमेटिक रूप से म्यूटेक्स को अनलॉक करने में सक्षम है, जांचें कि क्या Inform_one () को कॉल हुआ था और सो जाएं या नहीं। आप इसे मूर्ख नहीं कर सकते, लेकिन आप कर म्युटेक्स बंद कर दिया रखने के लिए की जरूरत है जब भी आप चर के परिवर्तन है कि को असत्य से सत्य हालत बदल सकते हैं और हो सकता है बनाने रखने , जबकि क्योंकि दौड़ की स्थिति के notify_one () कॉल यहाँ वर्णित है जैसे कि यह बंद कर दिया।
इस उदाहरण में हालांकि कोई शर्त नहीं है। मैंने शर्त 'गिनती == -1000' का उपयोग क्यों नहीं किया? क्योंकि यह सब यहाँ दिलचस्प नहीं है: जैसे ही -1000 पर पहुँच जाता है, हमें यकीन है कि कोई नया धागा 'सामान' क्षेत्र में प्रवेश नहीं करेगा। इसके अलावा, थ्रेड्स अभी भी स्टार्ट () और इन्क्रीमेंट काउंट (-999 और -998 आदि) को कॉल कर सकते हैं, लेकिन हम इस बारे में परवाह नहीं करते हैं। केवल एक चीज जो मायने रखती है वह यह है कि -1000 तक पहुंच गई थी - ताकि हम यह जान सकें कि 'डू स्टफ' क्षेत्र में अब कोई सूत्र नहीं हैं। हमें यकीन है कि यह तब होता है जब Inform_one () को कॉल किया जा रहा है, लेकिन यह सुनिश्चित करने के लिए कि हम रद्द करने से पहले सूचना_one () को कॉल नहीं करते हैं () अपने म्यूटेक्स को लॉक किया है? बस कुछ ही समय पहले inform_one () को रद्द करने से पाठ्यक्रम की मदद नहीं हो रही है।
समस्या यह है कि, के बावजूद हम एक शर्त के लिए इंतजार नहीं कर रहे हैं कि, वहां अभी भी है है एक शर्त है, और हम म्युटेक्स लॉक करने की आवश्यकता
1) इससे पहले कि हम 2 तक पहुंच जाए) इससे पहले कि हम Inform_one को कॉल करें।
सही कोड इसलिए बन जाता है:
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... वही शुरू () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
बेशक यह सिर्फ एक उदाहरण है लेकिन अन्य मामले बहुत अधिक हैं; लगभग सभी मामलों में जहां आप एक सशर्त चर का उपयोग करते हैं, आपको सूचित करने से पहले उस म्यूटेक्स लॉक (शीघ्र ही) की आवश्यकता होगी (या), या फिर यह संभव है कि आप प्रतीक्षा () को कॉल करने से पहले इसे कॉल करें।
ध्यान दें कि मैंने इस मामले में Inform_one () को कॉल करने से पहले mutex को अनलॉक किया था, क्योंकि अन्यथा वहाँ (छोटा) मौका है कि inform_one () को कॉल करने के लिए स्थिति चर के लिए इंतजार कर रहे धागे को जगाता है जो तब म्यूटेक्स और लेने की कोशिश करेगा ब्लॉक करें, इससे पहले कि हम म्यूटेक्स को फिर से जारी करें। यह जरूरत से थोड़ा धीमा है।
यह उदाहरण इस तरह से विशेष था कि स्थिति को बदलने वाली रेखा उसी थ्रेड द्वारा निष्पादित होती है जिसे प्रतीक्षा कहते हैं ()।
अधिक सामान्य स्थिति यह है कि एक धागा बस एक शर्त के सच होने की प्रतीक्षा करता है और दूसरा धागा उस स्थिति में शामिल चर को बदलने से पहले ताला लगा लेता है (जिससे संभवतः यह सच हो जाता है)। उस स्थिति में म्यूटेक्स को पहले (और बाद में) लॉक किया गया है, यह शर्त सही हो गई है - इसलिए उस मामले में सूचित करें * * () को कॉल करने से पहले सिर्फ म्यूटेक्स को अनलॉक करना पूरी तरह से ठीक है।
wait morphing
अनुकूलन को सक्षम करने के लिए ) अंगूठे के नियम ने इस लिंक में समझाया: लॉक के साथ सूचित करें स्थितियों में 2 से अधिक थ्रेड्स के साथ बेहतर पूर्वानुमानित परिणाम के लिए बेहतर है।