一、 为什么有了互斥锁还需要条件变量?
互斥锁解决了数据冲突,但无法解决“执行顺序”的问题。如果消费者一直抢锁去查仓库(死循环),会导致 CPU 空转(忙等待)。条件变量可以让线程在条件不满足时进入睡眠(阻塞),把 CPU 让出来,直到条件满足时被其他线程唤醒。
二、 核心 API 与易错点
pthread_cond_wait(cond, mutex):等待条件满足。pthread_cond_signal(cond):唤醒一个等待的线程。
面试极度高频:为什么 wait 必须传互斥锁?
因为在进入睡眠前,必须释放锁;在醒来后,必须重新获取锁。把“释放锁”和“进入睡眠”合并成一个原子操作,可以防止在释放锁后、入睡前,错过了生产者发出的唤醒信号。
为什么判断条件必须用 while 循环?
因为存在“伪唤醒”(操作系统层面的原因)或多个线程被同时唤醒,醒来后必须再次检查队列状态,否则会导致越界访问。
三、 生产者消费者模型(321原则)
3 种关系:生产者与生产者(互斥)、消费者与消费者(互斥)、生产者与消费者(同步+互斥)。
2 个角色:生产者、消费者。
1 个交易场所:阻塞队列(BlockingQueue)。
四、 实战:基于阻塞队列的并发控制
通过手写BlockQueue模板类,将生产者与消费者解耦。当队列满时,生产者阻塞;当队列空时,消费者阻塞。极大地提高了程序的健壮性和 CPU 利用率。
(选做:概述一下线程池的架构:提前创建好固定数量的线程,结合任务队列,一旦有任务丢进队列,线程就唤醒去抢任务执行,避免频繁创建销毁线程的开销)
自我实战操作
cd ~/linux_study mkdir sync_test cd sync_test实战 1:手写一个阻塞队列
vim BlockQueue.hpp(输入以下代码,结合了你之前学的锁和刚学的条件变量)
#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:用两个线程测试阻塞队列
vim main.cc(输入以下代码)
#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; }编译运行:
g++ main.cc -o main -lpthread ./main👉现象:因为消费者睡1秒,生产者0.2秒放一个,很快队列就满了(5个)。此时生产者的while循环生效,进入 wait 睡眠,不再疯狂刷屏。等消费者取走一个,生产者才醒过来放一个。这就是完美的同步控制!