【问题标题】:C++: condition-variable waitC++:条件变量等待
【发布时间】:2015-12-22 05:47:17
【问题描述】:
#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个步骤,'unlock,wait,lock',在signaled(wait return)和mutex.lock之间,不是原子操作,生产者线程可能会在wait()之前加锁mutex 锁mutex,并且activity仍然不为零,所以全局会增加,意外

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

【问题讨论】:

  • 恭喜您发布了一个完整的最小示例。它有很大帮助!

标签: c++ multithreading producer-consumer condition-variable


【解决方案1】:

你的问题是,当activity > 0 时,producer 可以循环获取锁,递增全局,并通知条件变量。 (通知不一定要有相应的服务员)。

您对thread.yield 的反复呼叫有点危险——这意味着您正在轮询,而不是等待。我认为解决方案是您需要 两个 条件变量。生产者等待一个,直到消费者通知,消费者等待另一个,直到生产者通知。不过,我不太确定您如何使这项工作与多个消费者一起使用。

【讨论】:

    【解决方案2】:

    我发现它有助于我在线程的上下文中做事。例如,如果您是客户,在等待什么?你,我的意思是在线程的上下文中。当你这样想时,使用monitors 编码变得非常简单。

    现在,正如 Martin 所说,我相信反复呼叫 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;
    }
    

    【讨论】:

    • @Benjamin 太好了,很高兴我能帮上忙!您会将问题标记为已回答吗?
    猜你喜欢
    • 2015-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 2011-10-10
    • 1970-01-01
    • 2021-08-23
    相关资源
    最近更新 更多