2016-01-05 49 views
5

编辑:看来,问题是我没有真正创建一个lock_guard的本地实例,而只是一个匿名临时的,它立即被再次销毁,正如评论指出的那样下面。std :: lock_guard导致未定义的行为

编辑2:启用clang的线程清理程序有助于在运行时查明这些类型的问题。它可以通过

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应该保证在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

@DarkFalcon这是意图抛出一个异常,如果客户端已经是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

相关问题