一、 为什么有了互斥锁还需要条件变量?
互斥锁解决了数据冲突,但无法解决"执行顺序"的问题。如果消费者一直抢锁去查仓库(死循环),会导致 CPU 空转(忙等待)。条件变量可以让线程在条件不满足时进入睡眠(阻塞),把 CPU 让出来,直到条件满足时被其他线程唤醒。
二、 核心 API 与易错点
-
pthread_cond_wait(cond, mutex):等待条件满足。 -
pthread_cond_signal(cond):唤醒一个等待的线程。
面试极度高频:为什么 wait 必须传互斥锁?
因为在进入睡眠前,必须释放锁;在醒来后,必须重新获取锁。把"释放锁"和"进入睡眠"合并成一个原子操作,可以防止在释放锁后、入睡前,错过了生产者发出的唤醒信号。
为什么判断条件必须用 while 循环?
因为存在"伪唤醒"(操作系统层面的原因)或多个线程被同时唤醒,醒来后必须再次检查队列状态,否则会导致越界访问。
三、 生产者消费者模型(321原则)
-
3 种关系:生产者与生产者(互斥)、消费者与消费者(互斥)、生产者与消费者(同步+互斥)。
-
2 个角色:生产者、消费者。
-
1 个交易场所:阻塞队列(BlockingQueue)。
四、 实战:基于阻塞队列的并发控制

通过手写 BlockQueue 模板类,将生产者与消费者解耦。当队列满时,生产者阻塞;当队列空时,消费者阻塞。极大地提高了程序的健壮性和 CPU 利用率。
(选做:概述一下线程池的架构:提前创建好固定数量的线程,结合任务队列,一旦有任务丢进队列,线程就唤醒去抢任务执行,避免频繁创建销毁线程的开销)
自我实战操作
bash
cd ~/linux_study
mkdir sync_test
cd sync_test
实战 1:手写一个阻塞队列
bash
vim BlockQueue.hpp
(输入以下代码,结合了你之前学的锁和刚学的条件变量)
cpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
template <typename T>
class BlockQueue {
public:
BlockQueue(int cap = 10) : _cap(cap) {
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond_pro, nullptr); // 给生产者准备的条件变量
pthread_cond_init(&_cond_con, nullptr); // 给消费者准备的条件变量
}
~BlockQueue() {
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond_pro);
pthread_cond_destroy(&_cond_con);
}
// 生产者接口
void Push(const T& in) {
pthread_mutex_lock(&_mutex);
// 如果队列满了,生产者就去睡觉
while (_q.size() >= _cap) {
pthread_cond_wait(&_cond_pro, &_mutex);
}
_q.push(in);
// 生产了东西,唤醒消费者
pthread_cond_signal(&_cond_con);
pthread_mutex_unlock(&_mutex);
}
// 消费者接口
void Pop(T* out) {
pthread_mutex_lock(&_mutex);
// 如果队列空了,消费者就去睡觉
while (_q.empty()) {
pthread_cond_wait(&_cond_con, &_mutex);
}
*out = _q.front();
_q.pop();
// 消费了东西,唤醒生产者
pthread_cond_signal(&_cond_pro);
pthread_mutex_unlock(&_mutex);
}
private:
std::queue<T> _q;
int _cap;
pthread_mutex_t _mutex;
pthread_cond_t _cond_pro;
pthread_cond_t _cond_con;
};
实战 2:用两个线程测试阻塞队列
bash
vim main.cc
(输入以下代码)
cpp
#include <iostream>
#include <unistd.h>
#include "BlockQueue.hpp"
void* Consumer(void* arg) {
BlockQueue<int>* bq = (BlockQueue<int>*)arg;
while (true) {
int data;
bq->Pop(&data);
std::cout << "消费者消费了数据: " << data << std::endl;
sleep(1); // 模拟消费处理耗时,消费慢,生产快
}
return nullptr;
}
void* Producer(void* arg) {
BlockQueue<int>* bq = (BlockQueue<int>*)arg;
int data = 1;
while (true) {
bq->Push(data);
std::cout << "生产者生产了数据: " << data << std::endl;
data++;
usleep(200000); // 生产快
}
return nullptr;
}
int main() {
BlockQueue<int>* bq = new BlockQueue<int>(5); // 仓库容量只有5
pthread_t c, p;
pthread_create(&c, nullptr, Consumer, (void*)bq);
pthread_create(&p, nullptr, Producer, (void*)bq);
pthread_join(c, nullptr);
pthread_join(p, nullptr);
return 0;
}
编译运行:
bash
g++ main.cc -o main -lpthread
./main

👉 现象 :因为消费者睡1秒,生产者0.2秒放一个,很快队列就满了(5个)。此时生产者的 while 循环生效,进入 wait 睡眠,不再疯狂刷屏。等消费者取走一个,生产者才醒过来放一个。这就是完美的同步控制!