消费者生产者多线程冻结
Posted
技术标签:
【中文标题】消费者生产者多线程冻结【英文标题】:Consumer producer multithreading freezes 【发布时间】:2021-05-28 05:23:00 【问题描述】:我正在尝试创建一个简单的消费者/生产者代码用于学习,其中生产者将数字推入堆栈,消费者线程打印数字,这就是我得到的:
const int N_THREADS = 10;
const int N_TESTS = 100;
bool finished = false;
queue<int> q;
void produce()
for (int i = 0; i < N_TESTS; i++)
q.push(i);
cv.notify_all();
finished = true;
void consume()
while (!q.empty() || !finished)
unique_lock<mutex> lock(m);
cv.wait(lock, [] return !q.empty(); );
int i = q.front();
cout << i << endl;
q.pop();
int main()
//thread that will be used for producing
thread producer(produce);
//vector of consumer threadss
vector<thread> consumers(N_THREADS);
for (int i = 0; i < N_THREADS; i++)
consumers[i] = thread(consume);
//joining all threads
producer.join();
for (int i = 0; i < N_THREADS; i++)
consumers[i].join();
return 0;
但是,当我运行代码时,它会打印数字但只是冻结,它永远不会结束:
可以做些什么来结束它?
【问题讨论】:
看起来生产者并没有保护自己免受队列的同时访问。while (return !q.empty() || !finished)
这是合法的吗?我以前从没见过这个……
std::queue
不是线程安全的数据结构。如果没有同步,您可能无法同时从多个线程中使用它。
@Borgleader 你是什么意思?
@cheveuxdelin 例如,仅在受互斥锁保护的关键部分内使用它。或者,您可以使用一些线程安全(可能是无锁)队列,但这些不是 C++ 本身提供的。
【参考方案1】:
我在您的代码中发现了一些错误。
首先为什么你调用notify_all()
你只推送了一个元素,我认为调用notify_one()
更好。
想象一下这个场景:
生产者推送一个元素,然后调用notify_all()
,然后其他线程唤醒其中一个线程将使用互斥锁并工作并弹出元素,然后其他线程检查条件并看到队列为空,然后它们将进入睡眠状态(浪费资源)。
另一个重要的错误是当生产者完成他的工作时它会在此时返回可能队列仍然有元素在这种情况下一些线程将等待通知但是由于生产者退出并且因此生产者将不再发送通知并且它会导致一些线程阻塞在cv.wai()
。即使是通知条件变量,它也会阻塞,因为!q.empty()
的条件返回 false(因为在生产者完成后q.empty()
已经为真)。
为了修复此代码,您应该修改代码的某些部分。
首先,我建议您在推送队列时也获取互斥锁,因为您可能会面临竞争条件(在这里您使用队列,我认为它不会在这里发生,但如果您使用向量,您肯定会看到) .
因为cv.wait()
处的代码块(但要注意一些线程将退出,因为它们会在生产者完成之前通知,并且在生产者完成之后他们将检查while条件并且他们看到finished变量为真,因此他们有机会退出但其他线程将在cv.wait()
处阻塞)。为了解决这个问题,你应该在你的条件变量中加入另一个条件:
cv.wait(lock, [] return !q.empty() || (finished && q.empty()); );
if (finished && q.empty())
break;
并且您的代码成功退出。
我还建议您阅读以下主题:
C++11 Can I ensure a condition_variable.wait() won't miss a notification?
https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables
【讨论】:
【参考方案2】:您的代码中的cv
和m
是什么?我可能猜到了,但Minimal, Reproducible Example 会更好。
consume
中的q.empty()
和finished
在没有保护的情况下访问。在该访问之前移动unique_lock<mutex> lock(m)
。 while 循环中的条件看起来是错误的。不应该是while (!finished)
吗?
void consume()
unique_lock<mutex> lock(m);
while (!finished)
cv.wait(lock, [] return !q.empty(); );
int i = q.front();
cout << i << endl;
q.pop();
produce
可以在无人时通知监听器。修改前不要锁定队列。
void produce()
unique_lock<mutex> lock(m);
for (int i = 0; i < N_TESTS; i++)
q.push(i);
lock.unlock();
cv.notify_all();
lock.lock();
finished = true;
【讨论】:
以上是关于消费者生产者多线程冻结的主要内容,如果未能解决你的问题,请参考以下文章