Edit: It seems like the problem was that I was not actually creating a local instance of lock_guard, but just an anonymous temporary one that was immediately destroyed, as noted in the comments below.
Edit2: Turning on the thread lock disinfectant helps identify these problems at runtime. It can be enabled using
clang++ -std=c++14 -stdlib=libc++ -fsanitize=thread *.cpp -pthread
This is probably a duplicate question in some way, but I couldn't find anything, so if it's really a duplicate, I'm sorry. In any case, it must be a beginner.
I played with a simple "Counter" class, say inline in a file
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
In main.cpp, it is assumed that the instance of the instance will increase and decrease at the same time:
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;
Exception handling seems to work the way it should, and as I understand it, std :: lock_guard should guarantee the unlock of the mutex after lock_guard goes out of scope.
However, this seems more complicated. While the increment correctly leads to the final value of "500", the decrement - which should lead to "0" - does not work. The result will be something between "0" and "16" or so.
If time changes, for example, using valgrind, it works correctly every time.
I was able to identify the problem using std :: lock_guard. If I define the decrement function () as follows:
void decrement() noexcept { mutex_.lock(); --value_;
everything works fine (until there is no shortage). But as soon as I make a simple change:
void decrement() noexcept { std::lock_guard<std::mutex>{mutex_}; --value_;
the behavior is similar to that described above. I believe that I really did not understand the behavior and use of the std :: lock_guard cases. I would really appreciate it if you could point me in the right direction!
The program is compiled through clang++ -std=c++14 -stdlib=libc++ *.cpp -pthread .