C++:条件变量等待

C++: condition-variable wait

本文关键字:等待 变量 条件 C++      更新时间:2023-10-16
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
std::mutex globalMutex;
std::condition_variable globalCondition;
int global = 0;
int activity = 0;
int CountOfThread = 1; // or more than 1
// just for console display, not effect the problem
std::mutex consoleMutex;
void producer() {
    while (true) {
        {
            std::unique_lock<std::mutex> lock(globalMutex);
            while (activity == 0) {
                lock.unlock();
                std::this_thread::yield();
                lock.lock();
            }
            global++;
            globalCondition.notify_one();
        }
        std::this_thread::yield();
    }
}

void customer() {
    while (true) {
        int x;
        {
            std::unique_lock<std::mutex> lock(globalMutex);
            activity++;
            globalCondition.wait(lock); // <- problem
            activity--;
            x = global;
        }
        {
            std::lock_guard<std::mutex> lock(consoleMutex);
            std::cout << x << std::endl;
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    for (int i = 0; i < CountOfThread; ++i) {
        std::thread(customer).detach();
    }
    std::thread(producer).detach();
    getchar();
    return 0;
}

我想要的是确保每次有客户线程获得增加的全局时,期望显示如下:1、2、3、...,但我看到的是全局值将在等待和活动之间增加--,因此,实际显示是:1、23、56、78、....

我发现问题出在wait((,在wait((中有3个步骤,"解锁,等待,锁定",在signaled(wait return(和mutex.lock之间,这不是原子操作,生产者线程可能会在wait((之前锁定互斥锁以锁定互斥锁,并且活动仍然不为零,因此全局将意外增加

有没有办法确定我的期望?

您的问题是,当activity> 0 时,producer可以循环抓取锁、递增全局并通知条件变量。 (通知不必有相应的服务员(。

您反复致电thread.yield有点危险信号 - 它们意味着您正在投票,而不是等待。 我认为解决方案是您需要两个条件变量。 生产者等待一个,直到被消费者通知,消费者等待另一个,直到生产者通知它。 不过,我不太确定您如何与多个消费者一起做到这一点。

我发现它可以帮助我在线程的上下文中做事。例如,如果您是客户,还在等什么?你的意思是,在线程的上下文中。当您这样思考时,使用监视器进行编码变得非常简单。

现在,正如马丁所说,我相信反复打电话给thread.yield有点可怕。这可能会导致可怕的代码交错。

为了展示代码不起作用的原因的示例,让我们快速交错:

  1. 创建了几个客户,抓住锁的客户增加了activity。然后该线程由于调用 wait 而进入睡眠状态。
  2. 另一个线程在初始客户线程调用wait后唤醒。这是因为wait解锁了传递到其中的互斥锁。
  3. 该线程获取globalMutex并增加activity。然后它等待。
  4. CountOfThread线程数重复此操作,因为这在多线程代码中是完全可能的。
  5. 最后,生产者线程运行,查看activity == 0,并解锁。但是,它没有notify_one(即信号(。然后它屈服了。这可能会导致代码未定义且令人困惑。

我的建议:

  1. 切勿在等待条件时打电话yield。这可能会导致复杂且难以阅读的无效监视器代码。
  2. 考虑每个线程正在等待的条件。如果不同的代码段正在等待不同的条件,请创建不同的锁。对于一段共享代码,只能使用一个锁。
  3. 在某些情况下,对不同的条件使用不同的条件变量。如果条件依赖于不同的数据,请务必使用不同的条件变量。

在您的情况下,解决方案并不像您想象的那么复杂。使用我最初的建议:在线程的上下文中思考。例如,当生产者线程正在运行时,当客户没有注意到global已更改时,它希望等待。当客户线程正在运行时,它希望等待生产者未更改global

下面是您想要的行为的示例:

mutex m;
condition_variable cv;
int global = 0, prev_global = 0;
void producer()
{
    while (true)
    {
        unique_lock<mutex> lock(m);
        while (prev_global != global)
        {
            cv.wait(lock);
        }
        prev_global = global++;
        cv.notify_one();
        lock.unlock();
    }
}
void customer()
{
    while (true)
    {
        unique_lock<mutex> lock(m);
        while (prev_global == global)
        {
            cv.wait(lock);
        }
        prev_global = global;
        cv.notify_one();
        lock.unlock();
    }
}
int main()
{
    vector<thread> pool;
    for (int i = 0; i < 5; ++i)
    {   
        pool.push_back(thread (customer));
    }
    pool.push_back(thread (producer));
    for (auto it = pool.begin(); it != pool.end(); ++it)
    {
        it->join();
    }
    return 0;
}