简介
在多线程编程中,生产者-消费者问题是一个经典的同步问题,它涉及到两种类型的线程:生产者和消费者。生产者负责生成数据放入缓冲区,而消费者则从缓冲区中取出数据。本文将详细介绍如何使用 C++11 的新特性来实现这一模型。
问题描述
生产者-消费者问题的关键在于如何协调生产者和消费者对共享缓冲区的访问,确保当缓冲区满时生产者停止生产,缓冲区空时消费者停止消费。
单消费者、生产者
类定义:Queue
首先,定义了一个名为Queue的类,它是生产者和消费者共享的队列。该队列内部使用std::queue来存储数据,并配有std::mutex和std::condition_variable来实现线程同步。
- put方法:当生产者生成一个新的物品时,首先会获取互斥锁。如果队列非空,生产者会等待消费者消费。然后,它会将物品放入队列,并通知等待的消费者可以开始消费了。
- get方法:消费者通过该方法从队列中取出物品。如果队列为空,消费者将等待生产者生产新的物品。一旦消费完成,它会通知可能在等待的生产者队列已空,可以继续生产。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std;
class Queue {
public:
void put(int val) {
lock_guard<std::mutex> guard(mtx);
if (!que.empty()) {
unique_lock<std::mutex> lck(mtx);
cv.wait(lck);
}
que.push(val);
cv.notify_all();
cout << "生产者 生产:" << val << "号物品" << endl;
}
int get() {
lock_guard<std::mutex> guard(mtx);
while (que.empty()) {
unique_lock<std::mutex> lck(mtx);
cv.wait(lck);
}
int val = que.front();
que.pop();
cv.notify_all();
cout << "消费者 消费:" << val << "号物品" << endl;
return val;
}
private:
queue<int> que;
mutex mtx;
condition_variable cv;
};
生产者和消费者行为
生产者和消费者通过调用 Queue 类的方法来进行生产和消费,其中包括等待和通知机制来协调操作:
void producer(Queue *que) {
for (int i = 1; i <= 10; ++i) {
que->put(i);
this_thread::sleep_for(chrono::milliseconds(100));
}
}
void consumer(Queue *que) {
for (int i = 1; i <= 10; ++i) {
que->get();
this_thread::sleep_for(chrono::milliseconds(100));
}
}
主函数
主函数创建生产者和消费者线程,并等待这些线程完成操作:
int main() {
Queue que;
thread t1(producer, &que);
thread t2(consumer, &que);
t1.join();
t2.join();
}
整体代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std;
std::mutex mtx;
std::condition_variable cv; //定义条件变量,做线程间的同步通信操作
class Queue {
public:
void put(int val) { //生产物品
lock_guard<std::mutex> guard(mtx);
if (!que.empty()) {
//que不为空,生产者应该通知消费者去消费,消费完了,在继续生产
//生产者线程应该进入#`等待状态,并且#2吧mtx释放掉
unique_lock<std::mutex> lck(mtx);
cv.wait(lck);
}
que.push(val);
/*
notify_one:通知另外一个线程的
notify_all:通知其他的所有线程的
通知其他所有的线程,我生产了一个物品,你们赶紧消费
其他线程得到该通知,就会从等待状态=》阻塞状态=》获取互斥锁才能继续执行
*/
cv.notify_all();
cout << "生产者 生产:" << val << "号物品" << endl;
}
int get() {// 消费物品
lock_guard<std::mutex> guard(mtx);
while (que.empty())
{
/* 消费者线程发现que是空的,通知生产者线程先生产物品 */
/*#1 进入等待状态 #2 把互斥锁mutex释放掉*/
unique_lock<std::mutex> lck(mtx);
cv.wait(lck);
}
int val = que.front();
que.pop();
cv.notify_all(); //通知其他线程我消费完了,赶紧生产吧
cout << "消费者 消费:" << val << "号物品" << endl;
return val;
}
private:
queue<int> que;
};
void producer (Queue *que) { //生产者线程
for (int i = 1; i <= 10; ++i) {
que->put(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer (Queue *que) { //消费者线程
for (int i = 1; i <= 10; ++i) {
que->get();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main () {
Queue que;
std::thread t1(producer, &que);
std::thread t2(consumer, &que);
t1.join();
t2.join();
}
多生产者消费者模型
该多生产者多消费者模型使用条件变量确保生产者在队列满时等待,消费者在队列空时等待。当条件满足(队列非满或非空)时,相应的生产者或消费者线程被唤醒。每个生产者和消费者都通过唯一的ID生成或处理不同的
改写Queue类
在多生产者多消费者的环境中,队列可能在任何时候被多个线程同时访问,因此我们需要确保队列的每个操作都是线程安全的,并且生产者不会在队列满时添加项目,消费者不会在队列空时尝试移除项目。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std;
class Queue {
public:
void put(int val) {
unique_lock<mutex> lck(mtx);
// 等待队列非满
while (que.size() == capacity) {
cout << "队列满,生产者等待" << endl;
cv_not_full.wait(lck);
}
que.push(val);
cout << "生产者 生产:" << val << "号物品" << endl;
cv_not_empty.notify_one(); // 通知至少一个等待的消费者
}
int get() {
unique_lock<mutex> lck(mtx);
// 等待队列非空
while (que.empty()) {
cout << "队列空,消费者等待" << endl;
cv_not_empty.wait(lck);
}
int val = que.front();
que.pop();
cout << "消费者 消费:" << val << "号物品" << endl;
cv_not_full.notify_one(); // 通知至少一个等待的生产者
return val;
}
private:
queue<int> que;
mutex mtx;
condition_variable cv_not_full, cv_not_empty;
static const size_t capacity = 10; // 设置队列容量限制
};
多生产者、多消费者线程
void producer(Queue* que, int id) {
for (int i = 0; i < 10; ++i) {
que->put(i + id * 100); // 确保生产的数字不重复
this_thread::sleep_for(chrono::milliseconds(100)); // 模拟生产延时
}
}
void consumer(Queue* que, int id) {
for (int i = 0; i < 10; ++i) {
que->get();
this_thread::sleep_for(chrono::milliseconds(100)); // 模拟消费延时
}
}
主函数
在主函数中,创建多个生产者和消费者线程。
int main() {
Queue que;
vector<thread> producers;
vector<thread> consumers;
for (int i = 0; i < 5; ++i) { // 创建5个生产者
producers.push_back(thread(producer, &que, i));
}
for (int i = 0; i < 5; ++i) { // 创建5个消费者
consumers.push_back(thread(consumer, &que, i));
}
for (auto& t : producers) {
t.join();
}
for (auto& t : consumers) {
t.join();
}
return 0;
}
整体代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std;
class Queue {
public:
void put(int val) {
unique_lock<mutex> lck(mtx);
// 等待队列非满
while (que.size() == capacity) {
cout << "队列满,生产者等待" << endl;
cv_not_full.wait(lck);
}
que.push(val);
cout << "生产者 生产:" << val << "号物品" << endl;
cv_not_empty.notify_one(); // 通知至少一个等待的消费者
}
int get() {
unique_lock<mutex> lck(mtx);
// 等待队列非空
while (que.empty()) {
cout << "队列空,消费者等待" << endl;
cv_not_empty.wait(lck);
}
int val = que.front();
que.pop();
cout << "消费者 消费:" << val << "号物品" << endl;
cv_not_full.notify_one(); // 通知至少一个等待的生产者
return val;
}
private:
queue<int> que;
mutex mtx;
condition_variable cv_not_full, cv_not_empty;
static const size_t capacity = 10; // 设置队列容量限制
};
void producer(Queue* que, int id) {
for (int i = 0; i < 10; ++i) {
que->put(i + id * 100); // 确保生产的数字不重复
this_thread::sleep_for(chrono::milliseconds(100)); // 模拟生产延时
}
}
void consumer(Queue* que, int id) {
for (int i = 0; i < 10; ++i) {
que->get();
this_thread::sleep_for(chrono::milliseconds(100)); // 模拟消费延时
}
}
int main() {
Queue que;
vector<thread> producers;
vector<thread> consumers;
for (int i = 0; i < 5; ++i) { // 创建5个生产者
producers.push_back(thread(producer, &que, i));
}
for (int i = 0; i < 5; ++i) { // 创建5个消费者
consumers.push_back(thread(consumer, &que, i));
}
for (auto& t : producers) {
t.join();
}
for (auto& t : consumers) {
t.join();
}
return 0;
}