多线程编程:生产者消费者模型
一、核心概念生产者-消费者模型是多线程同步中的经典场景用于解决“生产”与“消费”速度不匹配的问题核心由3个部分组成必须牢记生产者线程负责生成数据生产任务将数据放入共享缓冲区生产完成后通知消费者可存在多个生产者线程需保证对缓冲区的互斥访问。消费者线程负责从共享缓冲区中取出数据消费任务消费完成后释放缓冲区空间通知生产者可存在多个消费者线程同样需保证互斥访问缓冲区。共享缓冲区用于存储生产者生产的数据作为生产者和消费者的通信媒介有固定容量限制避免内存溢出常见实现方式队列queue、vector、数组面试中首选队列先进先出符合生产消费逻辑。核心目的解耦生产者和消费者平衡两者执行速度避免生产者生产过快导致缓冲区溢出或消费者消费过快导致无数据可消费同时通过同步机制保证线程安全提升系统并发效率。二、核心设计原则设计生产者-消费者模型时必须遵循3个核心原则线程安全原则生产者和消费者同时操作共享缓冲区必须通过互斥锁std::mutex保证同一时刻只有一个线程访问缓冲区避免竞态条件如多个生产者同时写入、多个消费者同时读取导致数据错乱、缓冲区异常。节奏控制原则通过条件变量std::condition_variable控制生产和消费节奏避免“忙等”提升CPU利用率缓冲区满时生产者停止生产进入等待状态等待消费者消费后通知缓冲区空时消费者停止消费进入等待状态等待生产者生产后通知资源回收原则确保所有生产者生产完毕后消费者能处理完缓冲区中剩余的数据避免数据遗漏所有线程执行完毕后正确回收线程资源join()避免内存泄漏。三、实现方式面试中主要考察2种核心实现Lambda简化版代码简洁首选、普通函数版逻辑清晰适配基础提问补充类封装版进阶考点均结合C11标准可直接用于面试代码题。实现方式1Lambda简化版核心优势无需单独定义生产者、消费者函数用Lambda直接作为线程函数结合互斥锁、条件变量代码简洁。#include iostream #include thread #include mutex #include condition_variable #include queue #include chrono // 模拟生产/消费耗时 using namespace std; // 1. 定义共享资源全局变量确保生命周期与线程一致 const int MAX_BUFFER_SIZE 5; // 缓冲区最大容量固定容量避免溢出 queueint buf; // 共享缓冲区队列先进先出符合生产消费逻辑 mutex mtx; // 互斥锁保护缓冲区访问安全 condition_variable cv; // 条件变量控制生产消费节奏 bool is_produce_over false; // 生产结束标志核心避免消费者提前退出 int main() { // 2. 生产者线程Lambda作为线程函数引用捕获共享资源 thread producer([]() { for (int i 1; i 10; i) { // 生产10个数据可灵活调整数量 // 加锁保证缓冲区访问互斥 unique_lockmutex lock(mtx); // 条件谓词缓冲区满时生产者等待避免溢出 cv.wait(lock, []() { return buf.size() MAX_BUFFER_SIZE; }); // 生产数据放入缓冲区 buf.push(i); cout 生产者生产 i 缓冲区当前大小 buf.size() endl; // 通知消费者缓冲区有数据可消费 cv.notify_all(); // 模拟生产耗时体现真实场景 this_thread::sleep_for(chrono::milliseconds(300)); } // 生产完毕设置标志位通知消费者处理剩余数据 is_produce_over true; cv.notify_all(); // 唤醒所有等待的消费者避免消费者永久阻塞 }); // 3. 消费者线程Lambda作为线程函数可多个消费者 thread consumer1([]() { while (true) { unique_lockmutex lock(mtx); // 条件谓词缓冲区空 生产未结束 → 消费者等待 cv.wait(lock, []() { return !buf.empty() || is_produce_over; }); // 终止条件生产结束 缓冲区空 → 退出消费 if (buf.empty() is_produce_over) { break; } // 消费数据从缓冲区取出 int data buf.front(); buf.pop(); cout 消费者1消费 data 缓冲区当前大小 buf.size() endl; // 通知生产者缓冲区有空闲位置可继续生产 cv.notify_all(); // 模拟消费耗时区分生产消费速度 this_thread::sleep_for(chrono::milliseconds(500)); } cout 消费者1消费完毕 endl; }); // 可新增多个消费者线程多生产者/多消费者场景 thread consumer2([]() { while (true) { unique_lockmutex lock(mtx); cv.wait(lock, []() { return !buf.empty() || is_produce_over; }); if (buf.empty() is_produce_over) { break; } int data buf.front(); buf.pop(); cout 消费者2消费 data 缓冲区当前大小 buf.size() endl; cv.notify_all(); this_thread::sleep_for(chrono::milliseconds(400)); } cout 消费者2消费完毕 endl; }); // 4. 等待所有线程执行完毕回收资源避免线程泄漏 producer.join(); consumer1.join(); consumer2.join(); cout 生产消费全部完成程序退出 endl; return 0; }实现方式2普通函数版核心优势逻辑拆分清晰适合面试官考察“函数拆分能力”与Lambda版核心逻辑一致仅将线程函数单独定义。#include iostream #include thread #include mutex #include condition_variable #include queue #include chrono using namespace std; // 共享资源全局变量简化写法 const int MAX_BUFFER_SIZE 5; queueint buf; mutex mtx; condition_variable cv; bool is_produce_over false; // 生产者函数单独定义 void producer() { for (int i 1; i 10; i) { unique_lockmutex lock(mtx); // 缓冲区满等待 cv.wait(lock, []() { return buf.size() MAX_BUFFER_SIZE; }); buf.push(i); cout 生产者生产 i 缓冲区大小 buf.size() endl; cv.notify_all(); this_thread::sleep_for(chrono::milliseconds(300)); } is_produce_over true; cv.notify_all(); } // 消费者函数单独定义 void consumer(int id) { // id区分多个消费者 while (true) { unique_lockmutex lock(mtx); // 缓冲区空且生产结束退出 cv.wait(lock, []() { return !buf.empty() || is_produce_over; }); if (buf.empty() is_produce_over) { break; } int data buf.front(); buf.pop(); cout 消费者 id 消费 data 缓冲区大小 buf.size() endl; cv.notify_all(); this_thread::sleep_for(chrono::milliseconds(500)); } cout 消费者 id 消费完毕 endl; } int main() { // 创建线程 thread prod(producer); thread cons1(consumer, 1); thread cons2(consumer, 2); // 等待线程结束 prod.join(); cons1.join(); cons2.join(); cout 生产消费完成 endl; return 0; }实现方式3类封装版适合考察“面向对象设计能力”将共享资源、生产消费逻辑封装到类中避免全局变量代码更规范体现工程实践能力。#include iostream #include thread #include mutex #include condition_variable #include queue #include chrono using namespace std; class ProducerConsumer { private: const int MAX_BUFFER_SIZE 5; queueint buf; // 缓冲区类内私有避免外部直接访问 mutex mtx; condition_variable cv; bool is_produce_over false; int produce_count 10; // 生产总数 public: // 生产者方法 void produce() { for (int i 1; i produce_count; i) { unique_lockmutex lock(mtx); cv.wait(lock, [this]() { return buf.size() MAX_BUFFER_SIZE; }); buf.push(i); cout 生产者生产 i 缓冲区大小 buf.size() endl; cv.notify_all(); this_thread::sleep_for(chrono::milliseconds(300)); } is_produce_over true; cv.notify_all(); } // 消费者方法 void consume(int id) { while (true) { unique_lockmutex lock(mtx); cv.wait(lock, [this]() { return !buf.empty() || is_produce_over; }); if (buf.empty() is_produce_over) { break; } int data buf.front(); buf.pop(); cout 消费者 id 消费 data 缓冲区大小 buf.size() endl; cv.notify_all(); this_thread::sleep_for(chrono::milliseconds(500)); } cout 消费者 id 消费完毕 endl; } }; int main() { ProducerConsumer pc; // 创建线程绑定类成员函数 thread prod(ProducerConsumer::produce, pc); thread cons1(ProducerConsumer::consume, pc, 1); thread cons2(ProducerConsumer::consume, pc, 2); prod.join(); cons1.join(); cons2.join(); cout 生产消费完成 endl; return 0; }四、高频易错点死锁风险原因1使用notify_one()替代notify_all()随机唤醒线程若唤醒的是同类型线程如生产者唤醒生产者会导致所有线程永久阻塞死锁原因2加锁顺序错误如生产者先加锁消费者也先加同一把锁无顺序问题但多把锁时易出错解决方案面试中优先使用notify_all()即使有惊群现象也能避免死锁若用notify_one()需确保唤醒的是异类型线程。虚假唤醒定义线程被唤醒后条件谓词仍然为假如消费者被唤醒但缓冲区仍为空导致线程执行无效逻辑解决方案必须使用带条件谓词的wait()接口cv.wait(lock, 谓词)而非无参wait()唤醒后先判断条件不满足则重新等待。生产结束标志位遗漏未设置is_produce_over标志位生产者生产完毕后消费者会因缓冲区空而永久阻塞无法退出解决方案必须添加生产结束标志消费者判断“缓冲区空生产结束”后退出。缓冲区容量设计缓冲区必须设置固定容量避免生产者无限制生产导致内存溢出面试中常问“缓冲区为什么要设容量”回答平衡生产消费速度避免内存溢出控制系统资源占用。线程资源回收所有线程必须调用join()避免线程泄漏面试中若代码遗漏join()会被判定为基础错误。捕获方式错误Lambda引用捕获局部变量如主线程局部的缓冲区主线程退出后子线程访问时出现野指针解决方案捕获全局变量、静态变量或值捕获需修改时加mutable。问题总结什么是生产者-消费者模型核心作用是什么生产者-消费者模型是多线程同步的经典场景由生产者生成数据、消费者处理数据、共享缓冲区存储数据三部分组成。核心作用是解耦生产者和消费者平衡两者执行速度避免生产过快导致缓冲区溢出、消费过快导致无数据可消费同时通过同步机制保证线程安全提升系统并发效率。生产者-消费者模型中互斥锁和条件变量的作用分别是什么① 互斥锁std::mutex保证共享缓冲区的互斥访问同一时刻只有一个线程生产者/消费者能操作缓冲区避免竞态条件如多个生产者同时写入、多个消费者同时读取② 条件变量std::condition_variable控制生产消费节奏避免忙等缓冲区满时让生产者等待缓冲区空时让消费者等待唤醒后继续执行提升CPU利用率。如何避免生产者-消费者模型中的死锁核心有3点① 优先使用notify_all()唤醒线程避免notify_one()随机唤醒同类型线程导致死锁② 确保条件谓词的完整性用带谓词的wait()接口规避虚假唤醒③ 所有线程执行完毕后调用join()回收线程资源④ 缓冲区设置固定容量避免生产者无限制生产。生产者-消费者模型中为什么要用unique_lock而不是lock_guard因为条件变量的wait()接口需要临时释放锁线程等待时释放锁让其他线程操作缓冲区而lock_guard不支持手动解锁无法满足wait()的需求unique_lock支持手动解锁、延迟加锁能配合wait()完成“释放锁-阻塞-唤醒-重新加锁”的流程是必须使用的锁类型。如果有多个生产者和多个消费者该如何设计核心不变只需创建多个生产者线程和多个消费者线程共享同一缓冲区、互斥锁和条件变量生产者之间竞争缓冲区的写入权限消费者之间竞争缓冲区的读取权限通过互斥锁保证互斥访问条件变量控制节奏需注意设置生产总数避免生产者无限生产同时确保所有生产者生产完毕后消费者能处理完剩余数据。