Использование condition_variable :: notify_all для уведомления нескольких потоков

Я пытался закодировать обедающих философов как способ стать лучше с многопоточным программированием. В моем коде у меня есть condition_variable это останавливает поток, пока все потоки не будут созданы. Тем не менее, кажется, что когда я звоню condition_variable::notify_all чтобы уведомить, что все потоки были созданы, и начать «есть», уведомляется только один поток. Например:

У меня есть класс Философов, который имеет следующие переменные-члены:

static std::condition_variable start;
static std::mutex start_mutex;

И эти функции-члены.

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...

Каждый поток ждет на condition_variable start и не буду продолжать, пока я не позвоню start_eating(), Проблема в том, что когда я звоню start.notify_all();только один из потоков получает уведомление и продолжает. Однако, когда я меняю код, чтобы разблокировать мьютекс после ожидания, все работает нормально (все потоки продолжаются):

    std::unique_lock lk{ start_mutex };
start.wait(lk);
lk.unlock();

Я не понимал, что здесь происходит. Зачем мне нужно разблокировать мьютекс?

Полный код:

#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;
}

2

Решение

Как объяснил Вот, призвание std::condition_variable::wait снимает блокировку, ждет, и после пробуждения блокировка повторно. Поэтому вам нужно разблокировать его вручную (или автоматически с помощью RAII), чтобы другие потоки могли его заблокировать. Условные переменные в C ++ имеют семантику, аналогичную неблокирующим мониторам, так что вы можете прочитать об этом. Кроме того, из-за ложной разблокировки, которая неизбежна, вы должны использовать другую версию функции, ту, которая использует предикат (больше информации в ссылке выше).

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]