2016-01-05 2 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 должен гарантировать разблокировку мьютекса после того, как 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

Смежные вопросы