I have been trying to code the dining philosophers as a way to get better with multithreading programming. In my code, I have a condition_variable that stops the thread until all the threads have been created. However, it seems that when I call condition_variable::notify_all to notify that all the threads have been created and to start 'eating', only one thread is notified. For example:
I have a Philosophers class which has these member variables:
static std::condition_variable start; static std::mutex start_mutex; And these member function.
static void start_eating() { start.notify_all(); } void dine() { signal(SIGINT, ctrl_c_catch); std::unique_lock lk{ start_mutex }; start.wait(lk); std::cout << id << "started\n"; // see end for complete class... Each thread waits on the condition_variable start and won't continue until I call start_eating(). The problem is that when I call start.notify_all();, only one of the threads gets notified and continues. However, when I change the code to unlock the mutex after waiting, everything runs OK (All the threads continue):
std::unique_lock lk{ start_mutex }; start.wait(lk); lk.unlock(); I was dont understand what is going on here. Why do I need to unlock the mutex?
The full code:
#include <chrono> #include <mutex> #include <vector> #include <thread> #include <condition_variable> #include <atomic> #include <signal.h> #include <iostream> #include <shared_mutex> #include <ctime> namespace clk = std::chrono; const auto EAT_SLEEP_TIME = clk::milliseconds{1}; // 5 seconds const auto NUM_SEATS = 5U; using Fork = std::mutex; // is the fork being used or not std::mutex cout_mutex; void ctrl_c_catch(int dummy); class Philosopher { Fork& left; Fork& right; unsigned id; unsigned times_eaten; static std::condition_variable start; static std::mutex start_mutex; static std::atomic_bool end; public: Philosopher(Fork& l, Fork& r, unsigned i) : left{ l }, right{ r }, id{ i }, times_eaten{} {} static void start_eating() { start.notify_all(); } static void stop_eating() { end = true; } void dine() { signal(SIGINT, ctrl_c_catch); std::unique_lock lk{ start_mutex }; start.wait(lk); // lk.unlock(); // uncommenting this fixes the issue std::cout << id << " started\n"; while (!end) { if (&right < &left) { right.lock(); left.lock(); } else { left.lock(); right.lock(); } cout_mutex.lock(); std::clog << id << " got both forks, eating\n"; cout_mutex.unlock(); ++times_eaten; std::this_thread::sleep_for(EAT_SLEEP_TIME * (rand() % 50)); right.unlock(); left.unlock(); std::this_thread::sleep_for(EAT_SLEEP_TIME * (rand() % 50)); } cout_mutex.lock(); std::cout << id << " stopped, terminating thread. Eaten " << times_eaten << "\n"; cout_mutex.unlock(); delete this; } }; std::atomic_bool Philosopher::end = false; std::condition_variable Philosopher::start{}; std::mutex Philosopher::start_mutex{}; template <size_t N, typename T = unsigned> constexpr std::array<T, N> range(T b = 0, T s = 1) { std::array<T, N> ret{}; for (auto& i : ret) { i = b; b += s; } return ret; } void ctrl_c_catch(int dummy) { std::cout << "Caught ctrl-c or stop\nStoping Philosophers\n"; Philosopher::stop_eating(); std::this_thread::sleep_for(clk::seconds{5}); exit(0); } int main() { srand(time(NULL)); signal(SIGINT, ctrl_c_catch); std::vector<Fork> forks{ NUM_SEATS }; // 5 forks std::vector<std::thread> phil; // vector of philosophers for (unsigned i : range<NUM_SEATS - 1>()) { auto p = new Philosopher{forks[i], forks[i + 1], i}; phil.emplace_back(&Philosopher::dine, p); } auto p = new Philosopher{forks[NUM_SEATS - 1], forks[0], NUM_SEATS - 1}; phil.emplace_back(&Philosopher::dine, p); std::clog << "Waiting for 5 seconds\n"; std::this_thread::sleep_for(clk::seconds{10}); std::clog << "Starting Philosophers\n Type 'stop' to stop\n"; Philosopher::start_eating(); for (auto& t : phil) t.detach(); std::this_thread::sleep_for(clk::seconds{15}); ctrl_c_catch(0); std::string dummy; std::cin >> dummy; if (dummy == "stop") ctrl_c_catch(0); return 0; }