3

我在代码审查堆栈交换中找到了这段代码,它实现了生产者-消费者问题。我在这里发布一段代码。

在给定的代码中,让我们考虑一个场景,当生产者通过调用产生一个值时void add(int num),它获取了互斥锁mubuffer.size()==size_这使得生产者由于条件变量而进入等待队列cond

同时,发生上下文切换,消费者调用函数int remove()来消费 value ,它试图获取 mutex 上的锁mu,但是锁之前已经被生产者获取,所以它失败并且永远不会消费 value,从而导致僵局。

我在哪里错了?因为当我运行它时代码似乎可以正常工作,所以调试它并没有帮助我。

谢谢

void add(int num) {
        while (true) {
            std::unique_lock<std::mutex> locker(mu);
            cond.wait(locker, [this](){return buffer_.size() < size_;});
            buffer_.push_back(num);
            locker.unlock();
            cond.notify_all();
            return;
        }
    }
    int remove() {
        while (true)
        {
            std::unique_lock<std::mutex> locker(mu);
            cond.wait(locker, [this](){return buffer_.size() > 0;});
            int back = buffer_.back();
            buffer_.pop_back(); 
            locker.unlock();
            cond.notify_all();
            return back;
        }
    }
4

2 回答 2

3

, 的想法std::condition_variable::wait(lock, predicate)是您等到满足谓词并在之后锁定互斥锁。要以原子方式执行此操作(这在大多数情况下很重要),您必须首先锁定互斥体,然后等待将释放它并锁定它以检查谓词。如果满足,互斥锁将保持锁定状态并继续执行。如果没有,互斥量将再次被释放。

于 2017-10-06T07:20:28.927 回答
3

OutOfBound 的答案很好,但是关于“原子”究竟是什么的更多细节是有用的。

对条件变量的wait操作有一个前置条件和一个后置条件,即传入的互斥锁被调用者锁定。该wait操作在内部解锁互斥锁,并且这样做的方式保证不会错过因解锁互斥锁而发生的来自其他线程的任何操作notifynotify_allwait互斥锁的解锁和进入等待通知的状态之间是原子的。这避免了睡眠/唤醒竞赛。

条件临界区形式在内部测试谓词。然而,它仍然取决于通知是否正确完成。

从某种意义上说,可以认为是wait这样做的:

while (!predicate()) {
    mutex.unlock();
    /* sleep for a short time or spin */
    mutex.lock();
}

带有通知的条件变量允许中间的注释行有效。这使:

while (!predicate()) {
    atomic { /* This is the key part. */
        mutex.unlock();
        sleep_until_notified();
    }
    mutex.lock();
}
于 2017-10-06T08:27:37.527 回答