Редактировать: Как мне кажется, проблема заключалась в том, что я фактически не создавал локальный экземпляр 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
.
@ DarkFalcon Это то, что предназначено. Исключено, что клиент пытается уменьшить значение, если оно уже равно 0. В способе инициализации экземпляра класса может быть только 0 или больше (size_t должен быть беззнаковым в любом случае), поэтому, если это 0, он должен просто оставаться таким образом. Этот вопрос не совсем о попытке реализовать идеальный счетчик в любом случае, а о том, как работает std :: unique_lock. – mbw
Вы можете получить лучшую производительность, если вместо блокировки мьютекса вы используете атомный прирост/декремент. – SergeyA