std::mutex无法解决生产者消费者同步问题,因其仅提供互斥访问,无法实现条件等待与精准唤醒;必须配合std::condition_variable和std::unique_lock,用while循环检查条件、在持锁时notify,且区分空满条件变量。

为什么直接用 std::mutex 无法解决生产者消费者同步问题
因为互斥锁只能保证「同一时间只有一个线程访问共享资源」,但无法让线程在条件不满足时主动等待、并在条件达成后被精准唤醒。比如消费者发现队列为空,它不能死循环 while (queue.empty()) 占着 CPU,也不能随便 sleep 一段固定时间——这既低效又不可靠。
真正需要的是:「挂起当前线程,释放锁,等别人通知我条件变了再重新抢锁继续执行」。这就是 std::condition_variable 的核心价值。
注意:std::condition_variable 必须和 std::unique_lock<:mutex></:mutex> 配合使用,不能配 std::lock_guard,也不能单独存在。
如何正确使用 wait() 避免虚假唤醒和逻辑错位
wait() 可能因系统信号等原因被唤醒(虚假唤醒),所以必须用 while 循环检查条件,而不是 if。同时,wait() 内部会自动释放锁,并在唤醒后重新加锁——这是关键机制。
立即学习“C++免费学习笔记(深入)”;
- 错误写法:
if (queue.empty()) cv.wait(lock);→ 可能跳过真实唤醒或误判 - 正确写法:
while (queue.empty()) cv.wait(lock);→ 每次唤醒都重新校验 - 别忘了:
cv.notify_one()或cv.notify_all()必须在修改完共享状态(如 push/pop)且仍持有锁时调用,否则消费者可能在 notify 前就 wait 进去了,导致丢失信号
生产者和消费者线程中 notify 的选择:one 还是 all
多数情况下用 cv.notify_one() 就够了——一个新元素入队,只需唤醒一个等待的消费者;一个元素出队,只需唤醒一个等待的生产者(如果用了有界队列)。用 notify_all() 会导致多个线程争抢锁和资源,徒增开销。
但要注意边界场景:
- 如果消费者处理非常慢,而生产者频繁 notify_one,可能导致部分消费者长期饥饿
- 若实现「广播式」语义(如关闭信号、清空指令),才需
notify_all() - 永远不要在无锁状态下调用 notify —— 它不保证线程安全,只是发信号,但前提是你已经确保状态已更新完毕
完整可运行的关键骨架(省略头文件和命名空间)
std::queue<int> q;
std::mutex mtx;
std::condition_variable cv_producer, cv_consumer;
const int MAX_SIZE = 10;
// 生产者
void producer() {
for (int i = 0; i < 20; ++i) {
std::unique_lock<std::mutex> lock(mtx);
while (q.size() == MAX_SIZE) {
cv_producer.wait(lock); // 等待队列有空位
}
q.push(i);
cv_consumer.notify_one(); // 通知至少一个消费者
}
}
// 消费者
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
while (q.empty()) {
cv_consumer.wait(lock); // 等待队列非空
}
int val = q.front();
q.pop();
lock.unlock(); // 提前释放锁,避免阻塞生产者
process(val); // 实际处理,不占共享锁
cv_producer.notify_one(); // 通知生产者队列有空位
}
}
这里有两个易忽略点:一是消费者在 process() 前主动 unlock(),避免处理耗时操作时霸占锁;二是两个 condition_variable 分开使用(cv_producer 和 cv_consumer),比共用一个更清晰、不易互相干扰——尤其在有界队列中,空和满是两个独立条件。











