2016-01-05 12 views
5

संपादित करें: ऐसा लगता है के रूप में, समस्या मुझे वास्तव में एक lock_guard की एक स्थानीय उदाहरण बनाकर नहीं किया गया था, लेकिन केवल एक गुमनाम अस्थायी है, जो फिर से तुरंत नष्ट कर दिया गया था, के रूप में टिप्पणी से बताया नीचे।std :: lock_guard के कारण अपरिभाषित व्यवहार

संपादित 2: क्लैंग के थ्रेड सैनिटाइज़र को सक्षम करने से रन-टाइम पर इन प्रकार की समस्याओं को दूर करने में मदद मिलती है। यह के माध्यम से

clang++ -std=c++14 -stdlib=libc++ -fsanitize=thread *.cpp -pthread 

सक्षम किया जा सकता है यह कोई डुप्लिकेट सवाल किसी तरह से शायद है, लेकिन मैं कुछ भी नहीं मिला है, इसलिए यदि यह वास्तव में नकल मैं माफी चाहता हूँ है। वैसे भी यह एक शुरुआती सवाल होना चाहिए।

मैं चारों ओर एक सरल "काउंटर" वर्ग के साथ खेल रहा था, फाइल में

Counter.hpp इनलाइन कहते हैं:

#ifndef CLASS_COUNTER_HPP_ 
#define CLASS_COUNTER_HPP_ 

#include <mutex> 
#include <string> 
#include <exception> 

class Counter 
{ 
    public: 
      explicit Counter(std::size_t v = 0) : value_{v} {} 

      std::size_t value() const noexcept { return value_; } 

//   void increment() { ++value_; }  // not an atomic operation : ++value_ equals value_ = value_ + 1 
               // --> 3 operations: read, add, assign 
      void increment() noexcept 
      { 
       mutex_.lock(); 
       ++value_; 
       mutex_.unlock(); 
      } 

//   void decrement() noexcept 
//   { 
//    mutex_.lock(); 
//    --value_;      // possible underflow 
//    mutex_.unlock(); 
//   } 

      void decrement() 
      { 
       std::lock_guard<std::mutex>{mutex_}; 
       if (value_ == 0) 
       { 
        std::string message{"New Value ("+std::to_string(value_-1)+") too low, must be at least 0"}; 
        throw std::logic_error{message}; 
       } 
       --value_; 
      } 

    private: 
      std::size_t value_; 
      std::mutex mutex_; 
}; 

#endif 

main.cpp में एक काउंटर उदाहरण माना जाता है बढ़ा दी है और कम कर समवर्ती:

main.cpp:

#include <iostream> 
#include <iomanip> 
#include <array> 
#include <thread> 
#include <exception> 

#include "Counter.hpp" 

    int 
main() 
{ 
    Counter counter{}; 
    std::array<std::thread,4> threads; 
    auto operation = [&counter]() 
    { 
      for (std::size_t i = 0; i < 125; ++i) 
       counter.increment(); 
    }; 
//  std::for_each(begin(threads),end(threads),[&operation](auto& val) { val = std::thread{operation}; }); 
    std::cout << "Incrementing Counter (" << std::setw(3) << counter.value() << ") concurrently..."; 
    for (auto& t : threads) 
    { 
      t = std::thread{operation}; 
    } 

    for (auto& t : threads) 
      t.join(); 
    std::cout << " new value == " << counter.value() << '\n'; 

    auto second_operation = [&counter]() 
    { 
      for (std::size_t i = 0; i < 125; ++i) 
      { 
       try 
       { 
        counter.decrement(); 
       } 
       catch(const std::exception& e) 
       { 
        std::cerr << "\n***Exception while trying to decrement : " << e.what() << "***\n"; 
       } 
      } 
    }; 

    std::cout << "Decrementing Counter (" << std::setw(3) << counter.value() << ") concurrently..."; 
    for (auto& t : threads) 
      t = std::thread{second_operation}; 
    for (auto& t : threads) 
      t.join(); 
    std::cout << " new value == " << counter.value() << '\n'; 

    return 0; 

अपवाद हैंडलिंग काम करता है जैसा कि यह माना जाता है, और जिस तरह से मैं इसे समझता हूं std :: lock_guard लॉक_गार्ड दायरे से बाहर हो जाने के बाद एक म्यूटक्स को अनलॉक करने की गारंटी देता है।

हालांकि यह उससे अधिक जटिल प्रतीत होता है। जबकि वृद्धि में "500" के अंतिम मूल्य में सही ढंग से परिणाम होता है, वसूली - जिसे "0" में परिणाम दिया जाता है - काम नहीं करता है। परिणाम "0" और "16" या उसके बीच कुछ होगा।

यदि समय बदल जाता है, उदाहरण के लिए valgrind का उपयोग करके, यह हर बार सही ढंग से काम करता प्रतीत होता है।

मैं समस्या को std :: lock_guard के उपयोग के लिए इंगित करने में सक्षम था। (जब तक कोई अधःप्रवाह है के रूप में)

 void decrement() noexcept 
     { 
      mutex_.lock(); 
      --value_;      // possible underflow 
      mutex_.unlock(); 
     } 

सब कुछ ठीक से काम करता है: यदि मैं इस रूप में घटती() फ़ंक्शन परिभाषित करते हैं। लेकिन एक बार मैं करने के लिए एक सामान्य परिवर्तन करना:

 void decrement() noexcept 
     {  
      std::lock_guard<std::mutex>{mutex_}; 
      --value_;      // possible underflow 
     } 

व्यवहार जैसे मैं ऊपर वर्णित है। मुझे लगता है कि मैं वास्तव में व्यवहार को समझ नहीं पाया और std :: lock_guard के मामलों का उपयोग नहीं किया। अगर आप मुझे सही दिशा में इंगित कर सकते हैं तो मैं वास्तव में इसकी सराहना करता हूं!

प्रोग्राम clang++ -std=c++14 -stdlib=libc++ *.cpp -pthread के माध्यम से संकलित करता है।

+0

@ डार्कफलकन यह वांछित है। क्लाइंट को एक अपवाद फेंक दिया जाता है, यदि ग्राहक पहले से ही 0 मान कम करने की कोशिश करता है। जिस तरह से क्लास इंस्टेंस प्रारंभ होता है, यह केवल 0 या अधिक हो सकता है (size_t को हस्ताक्षर नहीं किया जाना चाहिए वैसे भी), तो अगर यह 0 है, तो उसे बस इस तरह से रहना चाहिए। यह प्रश्न वास्तव में एक सही काउंटर को लागू करने की कोशिश करने के बारे में नहीं है, लेकिन यह समझने के बारे में कि std :: unique_lock कैसे काम करता है। – mbw

+2

यदि आप परमाणु वृद्धि/कमी का उपयोग करते हुए म्यूटेक्स को लॉक करने के बजाय आपको बेहतर प्रदर्शन मिल सकता है। – SergeyA

उत्तर

7

std::lock_guard<std::mutex>{mutex_}; स्थानीय नहीं बनाता है। यह एक अस्थायी बनाता है जो बयान के अंत में नष्ट हो जाता है। इसका मतलब है कि आपका मान लॉक द्वारा संरक्षित नहीं है।ताला गार्ड एक स्थानीय होना चाहिए:

void decrement() noexcept 
{  
    std::lock_guard<std::mutex> guard {mutex_}; 
    --value_;      // possible underflow 
} 
+0

यह बताता है। अपके इतने तेज़ जवाब के लिए आपका बहुत - बहुत धन्यवाद! – mbw

5

समस्या यह है कि लाइन

std::lock_guard<std::mutex>{mutex_}; 

एक चर का निर्माण नहीं करता है, बल्कि एक अस्थायी lock_guard वस्तु जो फिर तुरंत नष्ट हो जाता है बनाता है। क्या आप शायद लिखने के लिए मतलब था:।

std::lock_guard<std::mutex> guard{mutex_}; 

इस प्रकार lock_guard के एक चर, guard नाम है, जो नष्ट हो जाता है, जब यह समारोह के अंत में गुंजाइश (यानी छोड़ देता है बनाता है अनिवार्य रूप से, आप नाम देना भूल गए अपने परिवर्तनीय

+0

यह वास्तव में समस्या है, चीजों को जल्दी से साफ़ करने के लिए धन्यवाद! – mbw

संबंधित मुद्दे