生产者消费者c++ 讲解和代码示例

news/2024/10/21 13:38:34/

生产者-消费者问题的C++讲解和代码示例

一、问题描述

生产者-消费者问题是经典的多线程同步问题,涉及两个类型的线程:

  • 生产者线程:负责生成数据并放入共享缓冲区。
  • 消费者线程:负责从共享缓冲区取出数据进行处理。

关键挑战在于:

  • 同步:确保生产者和消费者在访问共享缓冲区时不发生冲突。
  • 互斥:防止多个线程同时修改缓冲区导致数据不一致。
  • 避免死锁:设计合理的等待和通知机制,防止线程无限期地等待。

二、解决方案

在C++中,可以使用以下同步机制:

  • std::mutex:互斥锁,用于保护共享数据的访问。
  • std::condition_variable:条件变量,用于线程间的等待和通知。
  • std::unique_lock<std::mutex>:配合条件变量使用的锁。

三、代码示例

下面是一个使用C++11线程库实现的生产者-消费者模型。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
std::queue<int> buffer; // 共享缓冲区
const unsigned int MAX_BUFFER_SIZE = 10; // 缓冲区最大容量void producer(int id) {int data = 0;while (true) {// 模拟生产数据的时间std::this_thread::sleep_for(std::chrono::milliseconds(100));std::unique_lock<std::mutex> lock(mtx);// 等待缓冲区未满cv.wait(lock, []() { return buffer.size() < MAX_BUFFER_SIZE; });// 生产数据并放入缓冲区buffer.push(data);std::cout << "生产者 " << id << " 生产了数据 " << data << std::endl;data++;// 通知消费者cv.notify_all();}
}void consumer(int id) {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待缓冲区不为空cv.wait(lock, []() { return !buffer.empty(); });// 从缓冲区取出数据int data = buffer.front();buffer.pop();std::cout << "消费者 " << id << " 消费了数据 " << data << std::endl;// 通知生产者cv.notify_all();// 模拟处理数据的时间lock.unlock(); // 解锁以允许生产者继续生产std::this_thread::sleep_for(std::chrono::milliseconds(150));}
}int main() {std::thread producers[2], consumers[2];// 启动生产者线程for (int i = 0; i < 2; ++i) {producers[i] = std::thread(producer, i);}// 启动消费者线程for (int i = 0; i < 2; ++i) {consumers[i] = std::thread(consumer, i);}// 等待线程完成(此示例中线程是无限循环,可根据需要修改)for (int i = 0; i < 2; ++i) {producers[i].join();consumers[i].join();}return 0;
}

四、代码解析

1. 全局变量

  • 互斥锁 mtx:保护对共享缓冲区的访问,防止数据竞争。
  • 条件变量 cv:用于线程间的等待和通知机制。
  • 共享缓冲区 buffer:存放生产者生成的数据,供消费者消费。
  • MAX_BUFFER_SIZE:限制缓冲区的最大容量,防止过度填充。

2. 生产者函数

void producer(int id) {int data = 0;while (true) {// 模拟生产时间std::this_thread::sleep_for(std::chrono::milliseconds(100));std::unique_lock<std::mutex> lock(mtx);// 等待缓冲区未满cv.wait(lock, []() { return buffer.size() < MAX_BUFFER_SIZE; });// 放入数据buffer.push(data);std::cout << "生产者 " << id << " 生产了数据 " << data << std::endl;data++;// 通知可能等待的消费者cv.notify_all();}
}
  • 使用unique_lock获取互斥锁,确保对缓冲区的独占访问。
  • 使用cv.wait等待缓冲区有空间(未满)。
  • 生产数据并放入缓冲区。
  • 使用cv.notify_all通知等待的消费者线程。

3. 消费者函数

void consumer(int id) {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待缓冲区不为空cv.wait(lock, []() { return !buffer.empty(); });// 取出数据int data = buffer.front();buffer.pop();std::cout << "消费者 " << id << " 消费了数据 " << data << std::endl;// 通知可能等待的生产者cv.notify_all();// 模拟处理数据的时间lock.unlock(); // 释放锁std::this_thread::sleep_for(std::chrono::milliseconds(150));}
}
  • 获取互斥锁,确保对缓冲区的独占访问。
  • 使用cv.wait等待缓冲区有数据(不为空)。
  • 消费数据并从缓冲区移除。
  • 使用cv.notify_all通知等待的生产者线程。
  • 在处理数据时释放锁,允许其他线程访问缓冲区。

4. 主函数

int main() {std::thread producers[2], consumers[2];// 启动生产者和消费者线程for (int i = 0; i < 2; ++i) {producers[i] = std::thread(producer, i);consumers[i] = std::thread(consumer, i);}// 等待线程完成(无限循环,实际应用中可添加终止条件)for (int i = 0; i < 2; ++i) {producers[i].join();consumers[i].join();}return 0;
}
  • 创建两个生产者线程和两个消费者线程。
  • 使用join等待线程完成(此示例中线程是无限循环)。

五、注意事项

  • 互斥锁的正确使用:确保在访问共享资源时始终持有互斥锁。
  • 条件变量的搭配使用cv.wait需要配合unique_lock和条件函数。
  • 避免虚假唤醒:条件函数应始终在循环中检查,cv.wait会自动处理这种情况。
  • 性能优化:根据实际需求调整缓冲区大小和线程数量。

六、总结

生产者-消费者问题是并发编程中的重要模型,通过C++的线程和同步机制,可以有效地实现线程间的协作。关键在于正确地使用互斥锁和条件变量,确保数据安全和线程同步。


http://www.ppmy.cn/news/1538461.html

相关文章

实时开放词汇目标检测(论文复现)

实时开放词汇目标检测&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 实时开放词汇目标检测&#xff08;论文复现&#xff09;概述模型框架使用方式配置环境训练和评估训练评估 演示效果Gradio Demo 概述 YOLO-World是由腾讯人工智能实验…

[C++][第三方库][RabbitMq]详细讲解

目录 1.介绍2.安装1.RabbitMq2.客户端库 3.AMQP-CPP 简单使用1.介绍2.使用 4.类与接口1.Channel2.ev 5.使用1.publish.cc2.consume.cc3.makefile 1.介绍 RabbitMQ&#xff1a;消息队列组件&#xff0c;实现两个客户端主机之间消息传输的功能(发布&订阅)核心概念&#xff1…

【云原生】容器方案 isula、containerd 基本功能测试

isula、containerd 基本功能测试 测试环境 树莓派 4BCPU: Cortex-A72 4CRAM: 8GSD卡: 128G C10 A1 U3OS: openEuler-22.03Docker: 20.10.17iSula: 2.0.7Containerd: 1.6.6 除 Docker 外没有集成网络组件&#xff0c;isula 与 containerd 均使用 host 网络测试。 测试情况 …

Docker 教程三 (Ubuntu Docker安装)

Ubuntu Docker 安装 Docker Engine-Community 支持以下的 Ubuntu 版本&#xff1a; Xenial 16.04 (LTS)Bionic 18.04 (LTS)Cosmic 18.10Disco 19.04 其他更新的版本…… Docker Engine - Community 支持上 x86_64&#xff08;或 amd64&#xff09;armhf&#xff0c;arm64&am…

部署 Docker harbor (httphttps)及使用

部署 Docker harbor (http/https)及使用 官网下载docker harbor 和docker-compose 下载最新版本即可 https://github.com/goharbor/harbor https://github.com/docker/compose/releases 一.Docker harbor 配置http使用 1.解压harbor.tar # 解压至指定目录 [rootdocker ~]…

无人机飞手执照培训,三类、四类傻傻分不清楚

无人机飞手执照培训中的三类和四类&#xff0c;主要依据无人机的空机重量进行区分&#xff0c;并对应不同的飞行权限和应用场景。以下是对这两类执照的详细解析&#xff1a; 一、无人机飞手执照的三类与四类定义 1. 三类执照&#xff1a; 定义&#xff1a;三类执照是指允许操…

多代理强化学习综述:原理、算法与挑战

1. 引言 多代理强化学习&#xff08;Multi-Agent Reinforcement Learning, MARL&#xff09;是强化学习的一个重要分支&#xff0c;它将传统的单代理强化学习概念扩展到多代理环境中。在MARL中&#xff0c;多个代理通过与环境和其他代理的交互来学习最优策略&#xff0c;以在协…

Go基础知识:切片

数组 Go 数组的大小是固定的&#xff0c;其长度是其类型的一部分&#xff08;[4]int并且[5]int是不同的、不兼容的类型&#xff09; var a [10]intb : [2]string{"Penn", "Teller"} b : [...]string{"Penn", "Teller"}package maini…