क्या मुझे condition_variable.notify_one () को कॉल करने से पहले लॉक प्राप्त करना होगा?


90

के उपयोग को लेकर मैं थोड़ा भ्रमित हूं std::condition_variable। मैं समझता हूं कि मुझे कॉल करने से पहले एक unique_lockपर बनाना होगा । मुझे जो नहीं मिल रहा है, वह यह कि क्या मुझे फोन करने से पहले एक अनोखा लॉक हासिल करना चाहिए या नहीं ।mutexcondition_variable.wait()notify_one()notify_all()

Cppreference.com पर उदाहरण परस्पर विरोधी हैं। उदाहरण के लिए, Inform_one पेज यह उदाहरण देता है:

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    cv.notify_one();

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) {
        lk.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
        std::cerr << "Notifying again...\n";
        cv.notify_one();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); t2.join();
}

यहां लॉक को पहले के लिए अधिग्रहित नहीं किया जाता है notify_one(), लेकिन दूसरे के लिए अधिग्रहित किया जाता है notify_one()। उदाहरणों के साथ अन्य पृष्ठों को देखते हुए मुझे अलग-अलग चीजें दिखाई देती हैं, जिनमें से ज्यादातर ताला प्राप्त नहीं करते हैं।

  • क्या मैं कॉल करने से पहले म्यूटेक्स को लॉक करने के लिए खुद को चुन सकता हूं notify_one(), और मैं इसे लॉक करने का विकल्प क्यों चुनूंगा ?
  • दिए गए उदाहरण में, पहले के लिए कोई लॉक क्यों नहीं है notify_one(), लेकिन बाद की कॉल के लिए है। क्या यह उदाहरण गलत है या कुछ औचित्य है?

जवाबों:


77

आपको कॉल करते समय लॉक रखने की आवश्यकता नहीं है condition_variable::notify_one(), लेकिन यह इस अर्थ में गलत नहीं है कि यह अभी भी अच्छी तरह से परिभाषित व्यवहार है और त्रुटि नहीं है।

हालांकि, यह एक "निराशावाद" हो सकता है क्योंकि जो भी प्रतीक्षा धागा रननीय है (यदि कोई हो) तुरंत लॉक को प्राप्त करने का प्रयास करेगा जो कि सूचित धागा रखता है। मुझे लगता है कि कॉल करते समय notify_one()या स्थिति चर के साथ जुड़े लॉक को रखने से बचने के लिए अंगूठे का एक अच्छा नियम है notify_all()Pthread Mutex देखें : pthread_mutex_unlock () उदाहरण के लिए बहुत समय व्यतीत करता है जहाँ notify_one()बेहतर प्रदर्शन के pread को बराबर कहने से पहले एक ताला जारी किया जाता है ।

ध्यान रखें कि लूप lock()में कॉल whileकुछ बिंदु पर आवश्यक है, क्योंकि while (!done)लूप की स्थिति की जांच के दौरान लॉक को आयोजित करने की आवश्यकता होती है । लेकिन इसे कॉल के लिए आयोजित करने की आवश्यकता नहीं है notify_one()


2016-02-27 : टिप्पणियों में कुछ प्रश्नों को संबोधित करने के लिए बड़ा अपडेट कि क्या कोई दौड़ की स्थिति है ताला notify_one()कॉल के लिए मदद नहीं है । मुझे पता है कि यह अपडेट देर से है क्योंकि यह प्रश्न लगभग दो साल पहले पूछा गया था, लेकिन मैं संभावित रेस की स्थिति के बारे में कुकी के प्रश्न को संबोधित करना चाहूंगा यदि निर्माता ( signals()इस उदाहरण में) notify_one()उपभोक्ता से पहले कॉल करता है ( waits()इस उदाहरण में) कॉल करने में सक्षम wait()

कुंजी यह है कि क्या होता है i- वह वस्तु जो वास्तव में इंगित करती है कि उपभोक्ता के पास "काम" है या नहीं। condition_variableसिर्फ एक उपभोक्ता कुशलता से करने के लिए एक बदलाव के लिए इंतजार जाने के लिए व्यवस्था नहीं है i

निर्माता को अपडेट iकरते समय लॉक को पकड़ना होगा, और उपभोक्ता को चेक iऔर कॉल करते समय लॉक को पकड़ना होगा condition_variable::wait()(यदि इसके लिए प्रतीक्षा करने की आवश्यकता है)। इस मामले में, कुंजी यह है कि जब उपभोक्ता यह चेक-एंड-वेट करता है, तो उसे लॉक (अक्सर एक महत्वपूर्ण अनुभाग कहा जाता है) को रखने का एक ही उदाहरण होना चाहिए । चूंकि महत्वपूर्ण खंड तब आयोजित किया जाता है जब निर्माता अपडेट करता है iऔर जब उपभोक्ता चेक-एंड-वेट करता है i, तो iउपभोक्ता के चेक iऔर जब कॉल करता है, तो इसके बीच बदलाव का कोई अवसर नहीं होता है condition_variable::wait()। यह हालत चर के उचित उपयोग के लिए क्रूक्स है।

C ++ मानक कहता है कि condition_variable :: प्रतीक्षा () निम्नलिखित के समान व्यवहार करती है जब एक विधेय के साथ कहा जाता है (जैसा कि इस मामले में):

while (!pred())
    wait(lock);

उपभोक्ता की जाँच के समय दो स्थितियाँ हो सकती हैं i:

  • यदि i0 है तो उपभोक्ता कॉल करता है cv.wait(), फिर iभी 0 तब होगा जब wait(lock)कार्यान्वयन का हिस्सा कहा जाता है - ताले का उचित उपयोग सुनिश्चित करता है। इस मामले में निर्माता के पास condition_variable::notify_one()अपने whileलूप में कॉल करने का कोई अवसर नहीं है जब तक कि उपभोक्ता ने कॉल नहीं किया है cv.wait(lk, []{return i == 1;})(और wait()कॉल ने सब कुछ ठीक से 'पकड़ने' के लिए करने की जरूरत है एक सूचना दी है - wait()जब तक कि उसने ऐसा नहीं किया है तब तक लॉक जारी नहीं करेगा। )। तो इस मामले में, उपभोक्ता अधिसूचना को याद नहीं कर सकता है।

  • अगर iउपभोक्ता कॉल करने पर पहले से ही 1 है cv.wait(), तो wait(lock)कार्यान्वयन का हिस्सा कभी नहीं बुलाया जाएगा क्योंकि while (!pred())परीक्षण आंतरिक लूप को समाप्त करने का कारण होगा। इस स्थिति में तब कोई फर्क नहीं पड़ता जब Inform_one () को कॉल लगती है - उपभोक्ता ब्लॉक नहीं करेगा।

यहाँ उदाहरण doneमें उत्पादक धागे पर वापस संकेत करने के लिए चर का उपयोग करने की अतिरिक्त जटिलता है जिसे उपभोक्ता ने पहचाना है i == 1, लेकिन मुझे नहीं लगता कि यह विश्लेषण को बिल्कुल बदल देता है क्योंकि सभी का उपयोग done(पढ़ने और संशोधित दोनों के लिए) ) जबकि एक ही महत्वपूर्ण खंडों में शामिल हैं iऔर शामिल हैं condition_variable

यदि आप उस प्रश्न को देखते हैं जो @ eh9 ने इंगित किया है, तो समन्वयन std :: atomic और std :: condition_variable का उपयोग करके अविश्वसनीय है , आपको एक दौड़ की स्थिति दिखाई देगी । हालाँकि, उस प्रश्न में पोस्ट किया गया कोड शर्त चर का उपयोग करने के मूलभूत नियमों में से एक का उल्लंघन करता है: यह चेक-एंड-वेट करते समय एक भी महत्वपूर्ण खंड नहीं रखता है।

उस उदाहरण में, कोड इस तरह दिखता है:

if (--f->counter == 0)      // (1)
    // we have zeroed this fence's counter, wake up everyone that waits
    f->resume.notify_all(); // (2)
else
{
    unique_lock<mutex> lock(f->resume_mutex);
    f->resume.wait(lock);   // (3)
}

आप देखेंगे कि wait()धारण करते समय # 3 पर प्रदर्शन किया जाता है f->resume_mutex। लेकिन wait()चरण # 1 के लिए आवश्यक है या नहीं, इसके लिए जांच उस ताले को पकड़ते समय बिल्कुल नहीं की जाती है (चेक-एंड-वेट के लिए बहुत कम लगातार), जो स्थिति चर के उचित उपयोग के लिए एक आवश्यकता है)। मेरा मानना ​​है कि जिस व्यक्ति को उस कोड स्निपेट की समस्या है, उसने सोचा कि चूंकि f->counterयह एक std::atomicप्रकार था , इसलिए यह आवश्यकता को पूरा करेगा। हालाँकि, इसके द्वारा प्रदान की गई परमाणुता std::atomicबाद की कॉल के लिए विस्तारित नहीं होती है f->resume.wait(lock)। इस उदाहरण में, जब f->counterजाँच की जाती है (चरण # 1) और जब wait()बुलाया जाता है (चरण # 3) के बीच एक दौड़ होती है ।

इस प्रश्न के उदाहरण में यह जाति मौजूद नहीं है।


2
इसके गहरे निहितार्थ हैं: domaigne.com/blog/computing/… उल्लेखनीय रूप से, आपके द्वारा उल्लिखित धार्मिक समस्या को हाल ही के संस्करण या सही झंडे के साथ निर्मित संस्करण द्वारा हल किया जाना चाहिए। ( wait morphingअनुकूलन को सक्षम करने के लिए ) अंगूठे के नियम ने इस लिंक में समझाया: लॉक के साथ सूचित करें स्थितियों में 2 से अधिक थ्रेड्स के साथ बेहतर पूर्वानुमानित परिणाम के लिए बेहतर है।
v.oddou

6
@ मिचेल: मेरी समझ से उपभोक्ता को अंततः कॉल करने की आवश्यकता है the_condition_variable.wait(lock);। यदि निर्माता और उपभोक्ता को सिंक्रोनाइज़ करने के लिए लॉक की आवश्यकता नहीं है (कहते हैं कि अंतर्निहित लॉक फ्री spsc queue है), तो उस लॉक का कोई उद्देश्य नहीं होता है यदि निर्माता इसे लॉक नहीं करता है। मेरी ओर से मंजूरी है। लेकिन क्या एक दुर्लभ दौड़ के लिए कोई जोखिम नहीं है? यदि निर्माता ताला नहीं लगाता है, तो क्या वह सूचित नहीं कर सकता है जबकि उपभोक्ता इंतजार से पहले है? तब उपभोक्ता इंतजार खत्म कर देता है और
कुकी

1
उदाहरण के लिए, उपरोक्त कोड में कहा गया है कि उपभोक्ता जिस std::cout << "Waiting... \n";समय पर है cv.notify_one();, उसके बाद जगा हुआ कॉल गायब हो जाता है ... या क्या मैं यहां कुछ याद कर रहा हूं?
कुकी

1
@Cookie। हाँ, वहाँ एक दौड़ की स्थिति है। देखें stackoverflow.com/questions/20982270/...
EH9

1
@ eh9: धिक्कार है, मुझे बस एक बार बग का कारण मिला जो मैंने आपकी टिप्पणी के लिए समय-समय पर मेरे कोड को फ्रीज किया था। यह दौड़ की स्थिति के इस सटीक मामले के कारण था। सूचना पूरी तरह से हल करने के बाद म्यूटेक्स को अनलॉक करना ... बहुत बहुत धन्यवाद!
गेलिनेट

10

परिस्थिति

Vc10 और बूस्ट 1.56 का उपयोग करते हुए मैंने एक समवर्ती कतार को लागू किया जैसे यह ब्लॉग पोस्ट बताता है। लेखक विवाद को कम करने के लिए म्यूटेक्स को अनलॉक करता है, अर्थात, notify_one()म्यूटेक्स अनलॉक किए गए के साथ कहा जाता है:

void push(const T& item)
{
  std::unique_lock<std::mutex> mlock(mutex_);
  queue_.push(item);
  mlock.unlock();     // unlock before notificiation to minimize mutex contention
  cond_.notify_one(); // notify one waiting thread
}

बूस्ट प्रलेखन में म्यूटेक्स को अनलॉक करना एक उदाहरण द्वारा समर्थित है :

void prepare_data_for_processing()
{
    retrieve_data();
    prepare_data();
    {
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

मुसीबत

फिर भी यह निम्नलिखित अनिश्चित व्यवहार का कारण बना:

  • जबकि notify_one()है नहीं बुलाया गया अभी तक cond_.wait()अभी भी के माध्यम से बाधित किया जा सकता हैboost::thread::interrupt()
  • एक बार notify_one()पहली बार cond_.wait()गतिरोध के लिए बुलाया गया था ; इंतजार से समाप्त नहीं किया जा सकता है boost::thread::interrupt()या boost::condition_variable::notify_*()अब।

समाधान

लाइन को हटाने से mlock.unlock()उम्मीद के मुताबिक कोड काम हो गया (नोटिफिकेशन और इंटरप्ट इंतजार खत्म हो गया)। ध्यान दें कि notify_one()अभी भी लॉक किए गए म्यूटेक्स के साथ कॉल किया जाता है, स्कोप को छोड़ते समय इसे बाद में अनलॉक किया जाता है:

void push(const T& item)
{
  std::lock_guard<std::mutex> mlock(mutex_);
  queue_.push(item);
  cond_.notify_one(); // notify one waiting thread
}

इसका मतलब है कि कम से कम मेरे विशेष थ्रेड कार्यान्वयन के साथ म्यूटेक्स को कॉल करने से पहले अनलॉक नहीं किया जाना चाहिए boost::condition_variable::notify_one(), हालांकि दोनों तरीके सही लगते हैं।


क्या आपने Boost.Thread को यह समस्या बताई है? मुझे ऐसा कोई कार्य नहीं मिल रहा है जिसमें svn.boost.org/trac/boost/…
magras

@ मगरास दुख की बात है कि मुझे इस पर कोई विचार नहीं आया। और दुर्भाग्य से मैं उल्लेखित कतार का उपयोग करके इस त्रुटि को दोहराने में सफल नहीं हुआ।
मैथ्यू ब्रैंडल

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

4

जैसा कि दूसरों ने बताया है, आपको notify_one()दौड़ की स्थिति और थ्रेडिंग से संबंधित मुद्दों के संदर्भ में, कॉल करते समय लॉक रखने की आवश्यकता नहीं है । हालांकि, कुछ मामलों में, लॉक को पकड़े जाने condition_variableसे पहले नष्ट होने से रोकने के लिए आवश्यकता हो सकती notify_one()है। निम्नलिखित उदाहरण पर विचार करें:

thread t;

void foo() {
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    t = std::thread([&]() {
        {
            std::lock_guard<std::mutex> l(m);  // (1)
            done = true;  // (2)
        }  // (3)
        cv.notify_one();  // (4)
    });  // (5)

    std::unique_lock<std::mutex> lock(m);  // (6)
    cv.wait(lock, [&done]() { return done; });  // (7)
}

void main() {
    foo();  // (8)
    t.join();  // (9)
}

मान लें कि tहमने इसे बनाने के बाद नव निर्मित धागे पर एक संदर्भ स्विच किया है, लेकिन इससे पहले कि हम स्थिति चर (कहीं (5) और (6) के बीच) पर प्रतीक्षा करना शुरू करें। थ्रेड tलॉक (1) का अधिग्रहण करता है, विधेय चर (2) सेट करता है और फिर लॉक (3) जारी करता है। मान लें कि notify_one()(4) निष्पादित होने से पहले इस बिंदु पर एक और संदर्भ स्विच सही है। मुख्य धागा लॉक (6) का अधिग्रहण करता है और लाइन (7) निष्पादित करता है, जिस बिंदु पर विधेय रिटर्न देता है trueऔर इंतजार करने का कोई कारण नहीं है, इसलिए यह लॉक को जारी करता है और जारी रहता है। fooरिटर्न (8) और इसके दायरे (सहित cv) में चर नष्ट हो जाते हैं। इससे पहले कि थ्रेड tमुख्य थ्रेड (9) में शामिल हो सकता है, उसे अपना निष्पादन समाप्त करना होगा, इसलिए यह जारी रहेगा जहां से इसे निष्पादित करना बाकी हैcv.notify_one()(४), जिस बिंदु पर cvवह पहले ही नष्ट हो चुका है!

इस मामले में संभावित सुधार कॉल करते समय लॉक को पकड़कर रखना है notify_one(यानी लाइन (3) में समाप्त होने वाले दायरे को हटा दें)। ऐसा करने से, हम यह सुनिश्चित करते हैं कि थ्रेड tकॉल notify_oneसे पहले cv.waitनए सेट की भविष्यवाणी चर की जाँच करें और जारी रखें, क्योंकि इसे लॉक को अधिग्रहित करने की आवश्यकता होगी, जो t वर्तमान में चेक को पकड़ रहा है। इसलिए, हम यह सुनिश्चित करते हैं कि रिटर्न के बाद cvथ्रेड द्वारा एक्सेस न किया जाए ।tfoo

संक्षेप में, इस विशिष्ट मामले में समस्या वास्तव में थ्रेडिंग के बारे में नहीं है, लेकिन संदर्भ द्वारा पकड़े गए चर के जीवनकाल के बारे में है। cvथ्रेड के माध्यम से संदर्भ द्वारा कैप्चर किया जाता है t, इसलिए आपको cvथ्रेड के निष्पादन की अवधि के लिए सुनिश्चित रहता है। यहां प्रस्तुत अन्य उदाहरण इस मुद्दे से ग्रस्त नहीं हैं, क्योंकि condition_variableऔर mutexवस्तुओं को वैश्विक दायरे में परिभाषित किया गया है, इसलिए उन्हें कार्यक्रम से बाहर निकलने तक जीवित रखने की गारंटी दी जाती है।


1

@ मिचेल बूर सही है। condition_variable::notify_oneचर पर ताला की आवश्यकता नहीं है। कुछ भी आपको उस स्थिति में लॉक का उपयोग करने से रोकता है, हालांकि उदाहरण इसे दिखाता है।

दिए गए उदाहरण में, ताला चर के समवर्ती उपयोग से प्रेरित है i। चूँकि signalsथ्रेड वैरिएबल को संशोधित करता है, इसलिए यह सुनिश्चित करने की आवश्यकता है कि उस दौरान कोई अन्य थ्रेड इसे एक्सेस न करे।

ताले का उपयोग किसी भी स्थिति में सिंक्रनाइज़ेशन की आवश्यकता के लिए किया जाता है , मुझे नहीं लगता कि हम इसे और अधिक सामान्य तरीके से बता सकते हैं।


बेशक, लेकिन इसके शीर्ष पर उन्हें भी हालत चर के साथ cunjunction में इस्तेमाल किया जाना चाहिए ताकि पूरे पैटर्न वास्तव में काम करता है। विशेष रूप से स्थिति परिवर्तनशील waitफ़ंक्शन कॉल के अंदर लॉक को रिलीज़ कर रहा है, और लॉक को पुनः प्राप्त करने के बाद ही वापस लौटाता है। किस बिंदु के बाद आप सुरक्षित रूप से अपनी स्थिति की जांच कर सकते हैं क्योंकि आपने "पढ़ने के अधिकार" प्राप्त कर लिए हैं। अगर इसकी अभी भी आप के लिए इंतजार कर रहे हैं नहीं, तो आप वापस करने के लिए जाओ wait। यह पैटर्न है। btw, यह उदाहरण इसका सम्मान नहीं करता है।
v.oddou

1

कुछ मामलों में, जब cv को अन्य थ्रेड्स द्वारा अधिग्रहित (लॉक) किया जा सकता है। आपको _ * () सूचित करने से पहले लॉक प्राप्त करना होगा और इसे जारी करना होगा।
यदि नहीं, तो सूचित करें _ * () शायद निष्पादित नहीं किया गया।


1

केवल इस उत्तर को जोड़ने के कारण मुझे लगता है कि स्वीकृत उत्तर भ्रामक हो सकता है। सभी मामलों में आपको अपने कोड को थ्रेड-सेफ़ होने के लिए कहीं न कहीं से अधिसूचित करने के लिए (_) कॉल करने से पहले म्यूटेक्स को लॉक करना होगा, हालाँकि आप वास्तव में सूचित _ * () को कॉल करने से पहले इसे फिर से अनलॉक कर सकते हैं।

स्पष्ट करने के लिए, आपको प्रतीक्षा (lk) दर्ज करने से पहले ताला लेना होगा क्योंकि प्रतीक्षा () अनलॉक lk है और यदि लॉक लॉक नहीं किया गया था तो यह अपरिभाषित व्यवहार होगा। यह सूचित_ओन () के साथ नहीं है, लेकिन आपको यह सुनिश्चित करने की आवश्यकता है कि आप प्रतीक्षा में प्रवेश करने से पहले आपको सूचित नहीं करेंगे _ * () और कॉल को म्यूटेक्स अनलॉक करने से पहले; जो स्पष्ट रूप से केवल सूचित करें * * () को कॉल करने से पहले उसी म्यूटेक्स को लॉक करके किया जा सकता है।

उदाहरण के लिए, निम्नलिखित मामले पर विचार करें:

std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;

void stop()
{
  if (count.fetch_sub(1) == -999) // Reached -1000 ?
    cv.notify_one();
}

bool start()
{
  if (count.fetch_add(1) >= 0)
    return true;
  // Failure.
  stop();
  return false;
}

void cancel()
{
  if (count.fetch_sub(1000) == 0)  // Reached -1000?
    return;
  // Wait till count reached -1000.
  std::unique_lock<std::mutex> lk(cancel_mutex);
  cancel_cv.wait(lk);
}

चेतावनी : इस कोड में एक बग है।

यह विचार निम्नलिखित है: थ्रेड्स कॉल स्टार्ट () और स्टॉप () जोड़े में, लेकिन केवल जब तक प्रारंभ () सही लौटे। उदाहरण के लिए:

if (start())
{
  // Do stuff
  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) // Reached -1000 ?
  {
    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 () को कॉल करने के लिए स्थिति चर के लिए इंतजार कर रहे धागे को जगाता है जो तब म्यूटेक्स और लेने की कोशिश करेगा ब्लॉक करें, इससे पहले कि हम म्यूटेक्स को फिर से जारी करें। यह जरूरत से थोड़ा धीमा है।

यह उदाहरण इस तरह से विशेष था कि स्थिति को बदलने वाली रेखा उसी थ्रेड द्वारा निष्पादित होती है जिसे प्रतीक्षा कहते हैं ()।

अधिक सामान्य स्थिति यह है कि एक धागा बस एक शर्त के सच होने की प्रतीक्षा करता है और दूसरा धागा उस स्थिति में शामिल चर को बदलने से पहले ताला लगा लेता है (जिससे संभवतः यह सच हो जाता है)। उस स्थिति में म्यूटेक्स को पहले (और बाद में) लॉक किया गया है, यह शर्त सही हो गई है - इसलिए उस मामले में सूचित करें * * () को कॉल करने से पहले सिर्फ म्यूटेक्स को अनलॉक करना पूरी तरह से ठीक है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.