C ++ 11:除非发布者休眠,否则发布者/消费者模式不会完成

时间:2018-06-06 07:58:50

标签: c++ multithreading c++11 mutex

在C ++中,我试图使用condition_variable获取发布者/消费者模式的句柄。这是我在网上看到的模板:

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <chrono>

using namespace std;

mutex m;
queue<string> que;
condition_variable cond;

void write(string &&msg) {
    unique_lock<mutex> locker(m);
    que.push(msg);
    locker.unlock();
    cond.notify_one();
    this_thread::sleep_for(chrono::milliseconds(1));
}

void read() {
    while (true) {
        unique_lock<mutex> locker(m);
        cond.wait(locker);
        if (!que.empty()) {
            cout << que.front() << endl;
            que.pop();
        }
        locker.unlock();
    }
}

void publisher(string &msg) {
    for (int i = 0; i < 100; ++i)
        write("Publisher: " + msg + ", " + to_string(i));
}

int main() {

    string msg = "Hello";
    thread pub_thread(publisher, std::ref(msg));

    /* The main thread will print the publisher's messages to cout. */
    read();

    /* Make the main thread wait for the publisher to finish. */
    pub_thread.join(); //
    return 0;
}

我不理解的是发布者线程上的sleep_for调用。我知道这仅仅是为了模拟一个真实的生活&#34;发布者不会如此快速地吐出消息的情况。但是,奇怪的是,如果我注释掉那一行,代码就不会运行完成。这是为什么?

此外,我尝试将sleep_for时间设置为0,效果相同。看来发表的从根本上需要睡觉,但我不明白为什么。为了更具体,代码应打印出100条消息。如果我让代码休眠1毫秒,则打印所有100条消息。如果我不这样做,那么在代码冻结之前我只会看到大约10条消息。似乎发生了死锁。

如果有更好的模式可以避免必须让出版商入睡......

我知道在实践中你需要有一个停止主线程的策略,比如毒丸。我故意忽略了这一点,专注于目前的讨论。

修改

嗯。如果我放入一个块来处理虚假唤醒,那就解决了这个问题。但这仍然无法解释原始代码失败的原因。

这是一个改进的读取功能:

void read() {
    while (true) {
        unique_lock<mutex> locker(m);
        cond.wait(locker, [&](){ return !que.empty(); });
        cout << que.front() << endl;
        que.pop();
        locker.unlock();
    }
}

2 个答案:

答案 0 :(得分:2)

当发布商的发布速度快于消费者消费时,您需要处理这种情况。

当发生这种情况时,消费者将错过condition_variable个触发器。请记住,notify来电不会累积。

将消费者更改为在唤醒后使用所有可用消息:

if (!que.empty())while (!que.empty())

像这样:

void read() {
    while (true) {
        unique_lock<mutex> locker(m);
        cond.wait(locker);
        while (!que.empty()) {
            cout << que.front() << endl;
            que.pop();
        }
        locker.unlock();
    }
}

答案 1 :(得分:1)

你有两个问题:一个是出版商没有义务屈服于读者 - 或者停留足够长的时间让读者成功锁定互斥锁 - 除非你做到了。

第二个是你的读者不正确:

void read() {
    while (true) {
        unique_lock<mutex> locker(m);
        cond.wait(locker);
        if (!que.empty()) {
            cout << que.front() << endl;
            que.pop();
        }
    }
}

这假设每个元素被推入队列时会得到一个“唤醒事件”,因为每次唤醒它只消耗一个元素。但那条件变量如何工作。

这个序列完全有可能发生:

  1. 发布商添加第1项
    • 发布者发出信号condvar
  2. 发布商添加第2项
    • 发布者发出信号condvar
  3. 读者从condvar等待中醒来
    • 读者使用第1项
  4. 在这种情况下,发布者将添加100个项目,并发出100次信号,但读者只会唤醒99次,因此最多可以消耗99个项目。

    正确的代码应该是这样的:

    void read() {
        unique_lock<mutex> locker(m);
        while (true) {
            // don't wait if we don't have to
            while (que.empty()) {
                cond.wait(locker);
            }
            // consume everything we can
            while (!que.empty()) {
                cout << que.front() << endl;
                que.pop();
            }
        }
    }
    

    使用谓词实现了大致相同的事情(为了清楚起见,我只是明确地写出了所有逻辑) - 第二个while在你的编辑中不存在,但是循环并跳过第一个而略微获得相同行为的更昂贵的方式。

    此外,没有必要在每次迭代时颠倒互斥锁 - 条件变量已经(un)根据需要锁定它。