自制C++生产者-消费者实现是否存在潜在问题?
首先,我先把你的代码整理成更清晰的可运行格式:
#include <iostream> #include <queue> #include <mutex> #include <condition_variable> #include <future> #include <cstdlib> std::mutex mtx; std::condition_variable cv; std::queue<int> Q; bool notified = false; bool done = false; void producer(int n) { std::unique_lock<std::mutex> lock(mtx); for (int i = 0; i < n; ++i) { cv.wait(lock, [] {return !notified; }); std::cout << "Producing.." << i << std::endl; Q.push(i); notified = true; cv.notify_one(); } done = true; } void consumer() { std::unique_lock<std::mutex> lock(mtx); while (!done) { cv.wait(lock, [] {return notified; }); while (!Q.empty()) { std::cout << "Consuming.." << Q.front() << std::endl; Q.pop(); cv.notify_all(); notified = false; } } } int main() { auto fut1 = std::async(producer, 20); auto fut2 = std::async(consumer); fut1.get(); fut2.get(); system("pause"); }
接下来,我们逐一拆解这个实现里的潜在问题:
1. 最严重的问题:生产者完成后消费者可能永久阻塞
当生产者做完20次生产后,它只是设置了done = true,但完全没有通知条件变量。如果此时消费者正卡在cv.wait(lock, [] {return notified; })这一行,它会永远停在这里——因为notified此时是false(上一次消费后设置的),生产者不会再修改这个变量或发送通知,而wait的谓词根本不会检查done的状态,消费者会一直阻塞,导致程序卡在fut2.get()处无法正常退出。
2. 消费者可能遗留未处理的队列元素
消费者的外层循环是while (!done),一旦生产者设置done = true,消费者就会退出这个循环。但如果此时队列里还有未处理的元素(虽然当前逻辑下队列最多只有1个元素,但逻辑稍有变动就可能出现积压),这些元素会被直接丢弃,永远不会被消费。
3. 串行化生产消费,完全浪费队列的缓冲能力
你的实现把生产和消费变成了严格的串行操作:生产者生产1个元素后,必须等消费者处理完这个元素(并把notified设为false)才能继续生产下一个。队列在这里几乎没起到缓冲作用,和直接的同步调用效率差不多,完全没发挥多线程的优势。
同时,消费者每次处理1个元素就调用cv.notify_all()并修改notified,会频繁唤醒生产者,造成不必要的上下文切换,增加性能损耗。
4. 变量设计的小瑕疵
虽然你在互斥锁保护下访问notified和done,内存可见性是有保障的,但把它们声明为std::atomic<bool>会更安全——能避免不小心在锁外访问这些变量时出现的内存可见性问题。另外,notified这个命名有点模糊,换成has_unconsumed_item之类的名字,代码可读性会好很多。
简单的修复建议
针对这些问题,你可以做以下调整:
- 生产者完成后必须通知条件变量:
done = true; cv.notify_all(); // 新增这一行,唤醒消费者 - 修改消费者的wait谓词,同时检查
done和notified:cv.wait(lock, [] {return notified || done; }); - 调整消费者的循环逻辑,确保处理完所有剩余元素再退出:
void consumer() { std::unique_lock<std::mutex> lock(mtx); // 只要生产未完成,或者队列还有元素,就继续运行 while (!done || !Q.empty()) { cv.wait(lock, [] {return notified || done; }); while (!Q.empty()) { std::cout << "Consuming.." << Q.front() << std::endl; Q.pop(); notified = false; } cv.notify_one(); // 处理完队列再通知生产者,减少不必要的唤醒 } } - 将
notified和done改为std::atomic<bool>,遵循多线程编程的最佳实践。
调整后,你的实现就能保证正确性,同时也能更合理地利用队列的缓冲能力。
内容的提问来源于stack exchange,提问作者Eduard Rostomyan

