在同一个互斥量上使用两个 std::unique_lock 会导致死锁?

Two std::unique_lock used on same mutex causes deadlock ?

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

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

而排队

同时发生上下文切换,consumer调用函数int remove()消费value,尝试获取mutexmu上的锁,但是之前已经获取了锁由生产者所以它失败并且永远不会消耗价值,因此导致死锁。

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

谢谢

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;
        }
    }

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

OutOfBound 的回答很好,但更详细地说明 "atomic" 是什么很有用。

对条件变量的wait操作有一个前置条件和一个后置条件,即传入的互斥锁被调用者锁定。 wait 操作在内部解锁互斥锁,并以一种保证不会错过任何因解锁互斥锁而发生的来自其他线程的 notifynotify_all 操作的方式进行。在 wait 中,互斥量的解锁和进入等待通知的状态对于彼此而言是原子的。这避免了 sleep/wakeup 比赛。

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

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

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

带notifies的条件变量让中间的注释行变得高效。这给出了:

while (!predicate()) {
    atomic { /* This is the key part. */
        mutex.unlock();
        sleep_until_notified();
    }
    mutex.lock();
}