在同一个互斥量上使用两个 std::unique_lock 会导致死锁?
Two std::unique_lock used on same mutex causes deadlock ?
我在实现生产者-消费者问题的代码审查堆栈交换中发现了这个 code。我在这里发布一段代码。
在给定的代码中,让我们考虑一个场景,当生产者通过调用 void add(int num)
产生一个值时,它获取了互斥量 mu
和 buffer.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
操作在内部解锁互斥锁,并以一种保证不会错过任何因解锁互斥锁而发生的来自其他线程的 notify
或 notify_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();
}
我在实现生产者-消费者问题的代码审查堆栈交换中发现了这个 code。我在这里发布一段代码。
在给定的代码中,让我们考虑一个场景,当生产者通过调用 void add(int num)
产生一个值时,它获取了互斥量 mu
和 buffer.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
操作在内部解锁互斥锁,并以一种保证不会错过任何因解锁互斥锁而发生的来自其他线程的 notify
或 notify_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();
}