c++ 使用condition_variable::notify_all通知多个线程

lkaoscv7  于 2022-12-20  发布在  其他
关注(0)|答案(1)|浏览(153)

我一直在尝试编写哲学家就餐程序,以提高多线程编程的水平。在我的代码中,我有一个condition_variable,它会在所有线程创建完毕之前停止线程。然而,当我调用condition_variable::notify_all通知所有线程都已创建完毕并开始"eating"时,似乎只通知了一个线程。例如:
我有一个Philosophers类,它有以下成员变量:

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

xzlaal3s1#

正如here所解释的,调用std::condition_variable::wait释放锁,等待,唤醒后,锁被重新获取,因此需要手动解锁(或自动使用RAII)来允许其他线程锁定它。C++中的条件变量具有与非阻塞监视器类似的语义,因此您可以阅读它以获得更好的直观理解。此外,由于伪解阻塞,这是不可能避免的,你应该使用另一个版本的函数,一个使用 predicate (更多信息在上面的链接)。

相关问题