代码之家  ›  专栏  ›  技术社区  ›  bremen_matt

C++ 11:除非发布者睡眠,否则发布者/用户模式不会完成。

  •  2
  • bremen_matt  · 技术社区  · 8 年前

    在C++中,我试图使用Apple获得发布者/用户模式的句柄。 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 调用发布者线程。我知道这只是为了模拟一个“真实生活”的场景,在这个场景中,出版商不会这么快地吐出消息。然而,奇怪的是,如果我注释掉那一行,代码就不会运行到完成。为什么会这样?

    此外,我尝试将sleep_for time设置为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 回复  |  直到 8 年前
        1
  •  2
  •   rustyx    8 年前

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

    当这种情况发生时,消费者会错过 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();
        }
    }
    
        2
  •  1
  •   Useless    8 年前

    您有两个问题:一个是发布者没有义务向读取器屈服,或者暂停足够长的时间让读取器成功地锁定互斥锁,除非您成功地做到了。

    第二是你的读者无论如何都是不正确的:

    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 wait唤醒
      • 读取器使用项1

    在这种情况下,发布者将添加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 在你的编辑中没有,但是循环和跳过第一次是一个稍微昂贵的方法来获得相同的行为。

    此外,不需要在每次迭代时都对互斥锁进行重击——条件变量已经(取消)根据需要将其锁定。