我在代码审查堆栈交换中找到了这个code,它实现了生产者 - 消费者问题。我在这里发布一段代码。
在给定的代码中,让我们考虑一个场景,当生产者通过调用void add(int num)
产生一个值,它获取锁定互斥mu
和buffer.size()==size_
这使得生产者继续等待队列到期到条件变量cond
。
同时,发生上下文切换,消费者调用函数int remove()
消耗值,它尝试获取互斥锁mu
上的锁,但是之前已经获取了锁。生产者因此失败并且从不消耗该值,从而导致死锁。
我在哪里错了?因为代码在我运行时似乎运行正常,所以调试它对我没有帮助。
由于
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;
}
}
答案 0 :(得分:3)
std::condition_variable::wait(lock, predicate)
的想法是,你要等到谓词满足并且之后锁定互斥锁。要以原子方式执行此操作(大多数情况下这很重要),您必须首先锁定互斥锁,然后等待将释放它并锁定它以检查谓词。如果满足,则互斥锁保持锁定状态并继续执行。如果没有,互斥锁将再次释放。
答案 1 :(得分:3)
OutOfBound的答案很好,但更确切地了解什么是“原子”是有用的。
对条件变量的wait
操作具有前提条件和后置条件,即传入的互斥锁被调用者锁定。 wait
操作在内部解锁互斥锁,并且保证不会错过因解锁互斥锁而导致的其他线程的任何notify
或notify_all
操作。在wait
内,互斥锁的解锁和进入等待通知的状态相互之间是原子的。这可以避免睡眠/唤醒比赛。
条件关键部分表单在内部测试谓词。但仍然取决于通知是否正确完成。
从某种意义上说,人们可以认为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();
}