Linux系统编程(20)——生产者-消费者模型基础示例(栈、队列实现、实现线程池)

生产者-消费者模型

多线程场景的的典型应用,应用场景非常广泛!手撕!!!

  • 消费者和消费者之间,是一个互斥关系。
  • 生产者和生产者之间,是一个互斥关系。
  • 生产者和消费者之间,是一个互斥同步关系。

注:同步互斥不一定非要用互斥锁和条件变量,还可以用信号量。


代码示例1:(栈实现,互斥锁/条件变量实现互斥同步)

#include <stdio.h>
#include<pthread.h> //头文件
#include <unistd.h>  //sleep头文件
#include <vector>

//定义一个互斥锁
pthread_mutex_t mutex;


//实现一个生产者消费者模型
//首先要有一个交易场所
std::vector<int> data;

//两个角色:生产者/消费者——两个线程
void* Product(void* arg) {
	(void*)arg;
	int count = 0;
	//负责把数据送到的交易场所中
	while (1)	{
		pthread_mutex_lock(&lock);
		data.push_back(++count);
		pthread_mutex_unlock(&lock);
		usleep(789789);
	}
	return NULL;
}

void* Consume(void* arg) {
	(void*)arg;
	//负责把交易场所中的数据获取出来
	while (1) {
		pthread_mutex_lock(&lock);
		//每次取最后一个元素
		//注意判空
		if (!data.empty()) {
			int result = data.back();
			data.pop_back();
			printf("result = %d\n", result);
		}
		pthread_mutex_unlock(&lock);
		usleep(123123);
	}
	return NULL;
}


int main() {

	pthread_mutex_init(&lock, NULL);//互斥锁初始化函数

	pthread_t tid1, tid2;
	pthread_create(&tid1, NULL, Product, NULL); //创建新线程
	pthread_create(&tid2, NULL, Consume, NULL);

	pthread_join(tid1,NULL); //线程等待
	pthread_join(tid2, NULL);


	pthread_mutex_destory(&lock);//互斥锁释放函数
	system("pause");
	return 0;
}

//不加互斥锁:
//但是使用过多线程,就有可能有段错误。
//因为C++STL中所提供的容器和算法都是线程不安全[重要]

//加上互斥锁:保证线程安全。

结果:

实现了不断加1,打印。但是使用过多线程,就有可能有段错误。因为 C++STL 中所提供的容器算法都是线程不安全[重要]。加上互斥锁就好了。

刚刚互斥锁这是实现了:消费者和生产者的互斥关系,效率不是很高。消费者做了很多无用功,因为他的速度更快。这时候可以同步进一步改进。如果vector 没数据,消费者就等,有数据才真正进行消费。


下面是加上条件变量——即实现同步。谁快谁等。

完整代码:

#include <stdio.h>
#include<pthread.h> //头文件
#include <unistd.h>  //sleep头文件
#include <vector>

//定义一个互斥锁
pthread_mutex_t mutex;
//定义同步——条件变量
pthread_cond_t cond;


//实现一个生产者消费者模型
//首先要有一个交易场所
std::vector<int> data;

//两个角色:生产者/消费者——两个线程
void* Product(void* arg) {
	(void*)arg;
	int count = 0;
	//负责把数据送到的交易场所中
	while (1)	{
		pthread_mutex_lock(&lock);
		data.push_back(++count);
		pthread_mutex_unlock(&lock);
		pthread_cond_signal(&lock); //去通知
		usleep(789789);
	}
	return NULL;
}

void* Consume(void* arg) {
	(void*)arg;
	//负责把交易场所中的数据获取出来
	while (1) {
		pthread_mutex_lock(&lock);
		//每次取最后一个元素
		//注意判空,因为快,所以等
//这里用while 的原因是:pthread_cond_wait()不一定返回的是其他线程的signal
//有可能被信号打断
		while(data.empty()) {
			//1.释放锁
			//2.等待条件就绪(其他线程调用 pthread_cond_signal)   //1,2原子的
			//3.条件就绪了,重新获取锁
			//加上wait的意义:没有数据,消费者数据不会空转,节省了资源。
			pthread_cond_wait(&cond, &lock);
		}

		int result = data.back();
		data.pop_back();
		printf("result = %d\n", result);
		pthread_mutex_unlock(&lock);
		usleep(123123);
	}
	return NULL;
}


int main() {

	pthread_mutex_init(&lock, NULL);//互斥锁初始化函数
	pthread_cond_init(&cond, NULL);//条件变量初始化函数

	pthread_t tid1, tid2;
	pthread_create(&tid1, NULL, Product, NULL); //创建新线程
	pthread_create(&tid2, NULL, Consume, NULL);

	pthread_join(tid1,NULL);  //线程等待
	pthread_join(tid2, NULL);

	
	pthread_cond_destory(&cond);//条件变量释放函数
	pthread_mutex_destory(&lock);	
	return 0;
}

//不加互斥锁:
//但是使用过多线程,就有可能有段错误。
//因为C++STL中所提供的容器和算法都是线程不安全[重要]

//加上互斥锁:保证线程安全。

这是最简单的生产者—消费者模型。

总结:一二三

  • 一:一个交易场所
  • 二:两个角色
  • 三:三种关系(生产者之间—互斥关系;消费者之间—互斥关系;生产者和消费者—互斥同步关系)


代码示例2:(队列实现,信号量实现互斥同步)

信号量:

就是一个计数器,表示资源的个数。

  • p 申请资源,计数 -1
  • v 释放资源,计数器 +1
  • 当计数器是 0,再去 p 操作就会阻塞

用信号量:

表示互斥:P  V 操作在同一个函数中

表示同步:P  V 操作不在同一个函数中

#pragma once

#include <stdio.h>
#include<pthread.h> //头文件
#include <unistd.h>  //sleep头文件
#include <vector>

//同步互斥不一定非要用互斥锁和条件变量
//信号量:就是一个计数器,表示资源的个数。
//p 申请资源,计数 - 1
//v 释放资源,计数器 + 1
//当计数器是 0,再去 p 操作就会阻塞

//信号量表示互斥比较简单,同步就很复杂。

//初始化信号量:
#include <semaphore.h>  //信号量头文件
sem_t sem;

//阻塞队列
//一般是由上限的:队列为空,执行 Pop 会阻塞
//队列满了,执行 Push 会阻塞
template<typename T>
class BlockingQueue
{
public:
	BlockingQueue(int max_size)  //构造函数
		:max_size_(max_size),head_(0),tail_(0),size_(0),
			queue_(max_size){   //queue(max_size):含义是将元素个数设置为 max_size
		sem_init(&lock_, 0, 1); //初始化信号量
		sem_init(&elm_, 0, 0); //初始化信号量
		sem_init(&blank_, 0, max_size); //初始化信号量
	}
	~BlockingQueue() {  //析构函数
		sem_destory(&lock_);
		sem_destory(&elm_);
		sem_destory(&blank_);
	}

	
	void Push(const T& data) {
		//每次插入元素先申请空格资源,没有空格资源,信号量0,说明满了,不能插入,push中阻塞。
		sem_wait(&blank_);

		sem_wait(&lock_);
		queue_[tail_] = data;
		++head_;
		++size_;
		sem_post(&lock_);

		sem_post(&elm_);   //元素资源 +1
	}

	//data 表示出队列的这个元素
	void Pop(T* data) {
		//每次删除元素先申请元素资源,没有元素资源,信号量0,说明为空,不能删除,pop中阻塞。
		sem_wait(&elm_);

		sem_wait(&lock_);
		*data = queue_[head_];
		++head_;
		--size_;
		sem_post(&lock_);  //这和互斥锁没区别

		sem_post(&blank_);  //空格资源 +1 
	}


private:
	std::vector<T> queue_;
	int head_;
	int tail_;
	int size_;
	int max_tail;
	sem_t lock_;  //信号量  
	sem_t elm_;    //元素个数
	sem_t blank_;	//空格个数
};
//用一个二元信号量(非0 或 1)表示互斥锁
//一个信号量表示当前队列中元素的个数
//一个信号量表示当前队列中空格的个数,
//插入元素就是消耗一个空格资源,释放一个元素资源
//删除元素消耗一个元素资源,释放一个空格资源



/**********************************
***********以上都在头文件中
**********************************/

#include"BlockQueue.hpp"

BlockingQueue<int> queue(100);

//两个角色:生产者/消费者——两个线程
void* Product(void* arg) {
	(void*)arg;
	int count = 0;
	//负责把数据送到的交易场所中
	while (1)	{
		queue.Push(++count);
		usleep(789789);
	}
	return NULL;
}

void* Consume(void* arg) {
	(void*)arg;
	//负责把交易场所中的数据获取出来
	while (1) {
		int count = 0;
		queue.Pop(&count);
		printf("count = %d\n", result);
		usleep(123123);
	}
	return NULL;
}




int main() {
	pthread_t tid1, tid2;
	pthread_create(&tid1, NULL, Product, NULL); //创建新线程
	pthread_create(&tid2, NULL, Consume, NULL);

	pthread_join(tid1, NULL);  //释放资源
	pthread_join(tid2, NULL);

	return 0;
}

结果就会逐条打印:count = 1 ……;重点理解这个过程,方法模型。

生产者消费者模型实现——线程池

/************************************************************************/
/*在头文件中                                                            */
/************************************************************************/

#pragma once
#include <stdio.h>
#include "Blockingqueue.hpp" //之前的队列
#include <vector>
#include<unistd.h>
class Task {
public:
	virtual void Run() { //虚函数
		printf("base Run\n");
	}
	Task();
	~Task() {

	}
protected:
	
private:
	
};

//线程池启动的时候会创建一组进程
//每个线程都需要完成一定的任务(执行一定的代码逻辑,这个逻辑调用者来决定)
//任务就是一段代码,可以用函数来表示
class ThreadPool {
public:
	//n 表示创建线程的数量
	ThreadPool(int n) :queue_(100){
		//创建出若干线程
		for (int i = 0; i < worker_count_;++i) {
			pthread_t tid;
			pthread_creat(&tid, NULL, ThreadEntry, this);
			worker_.push_back(tid);
		}
	}
	virtual ~ThreadPool(){
		//先让线程退出,然后回收
		for (size_t i = 0; i < workers_.size(); ++i) {
			pthread_cancel(workers_[i]);
		}

		for (size_t i = 0; i < workers_.size();++i) {
			pthread_join(workers_[i], NULL);
		}
		
	}

	//使用线程池的时候,就需要调用者加入一些任务,让线程池去执行
	void AddTask(Task* task) {  //添加任务函数
		queue_.Push(task);
	}
	
private:
	BlockingQueue<Task*> queue_;
	int worker_count_;
	std::vector<pthread_t> workers_;

	static void* ThreadEntry(void* arg) {
		ThreadPool* pool = (ThreadPool*)arg;
		while (true) {
			//循环中尝试从阻塞队列中获取到一个任务,并且执行
			Task* task = NULL;
			pool->queue_.Pop(&task);
			//表面是是task*,实际指向 Mytask*
			//执行子类,用户自定义的逻辑
			task->Run();
			delete task;
		}
	}
};



/************************************************************************/
/*                                                                      */
/************************************************************************/

#include "threadpool.hpp"

//这个类是用户自定制,需要依赖那个数据自定义添加修改
class MyTask:public Task {
public:
	MyTask(int id) :id_(id) {

	}
	~MyTask() {

	}
	void Run() {
		//执行用户自定义的逻辑
            printf("id =%d\n",id_);
	}
private:
	int id_;
};


int main() {
	ThreadPool pool( 10);
	for (int i = 0; i < 20;++i) {
		pool.AddTask(new MyTask(i));
	}
	while (1) {
		Sleep(1);
	}
	
	return 0;
}

结果是:打印id =0 ~20的结果

线程池的好处:(和单个线程使用比较)

  • 提前把线程创建好,避免反复的创建销毁线程的开销
  • 线程不必创建太多,复用一个线程完成多个任务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
生产者-消费者问题是一个经典的多线程并发问题,主要涉及到生产者线程和消费者线程之间的协作与同步。在这个问题中,生产者线程负责生产数据,并将数据存入一个共享的缓冲区中,而消费者线程则从缓冲区中取出数据进行消费。为了避免生产者消费者线程之间的竞争条件和死锁问题,需要使用线程同步技术。 下面是一个使用 Python 语言实现生产者-消费者问题的示例代码: ```python import threading import time import random # 缓冲区大小 BUFFER_SIZE = 5 # 共享的缓冲区 buffer = [] # 生产者线程 class ProducerThread(threading.Thread): def run(self): global buffer while True: # 生产一个随机数 item = random.randint(1, 10) print("生产者生产了数据:", item) # 获取锁 lock.acquire() # 如果缓冲区已满,等待消费者线程消费数据 while len(buffer) >= BUFFER_SIZE: print("缓冲区已满,生产者等待...") lock.wait() # 将数据存入缓冲区 buffer.append(item) print("生产者将数据存入缓冲区:", buffer) # 释放锁 lock.release() # 随机等待一段时间 time.sleep(random.randint(1, 3)) # 消费者线程 class ConsumerThread(threading.Thread): def run(self): global buffer while True: # 获取锁 lock.acquire() # 如果缓冲区为空,等待生产者线程生产数据 while len(buffer) == 0: print("缓冲区为空,消费者等待...") lock.wait() # 从缓冲区取出数据进行消费 item = buffer.pop(0) print("消费者消费了数据:", item) # 释放锁 lock.release() # 随机等待一段时间 time.sleep(random.randint(1, 3)) # 创建锁 lock = threading.Condition() # 创建生产者线程和消费者线程 producer_thread = ProducerThread() consumer_thread = ConsumerThread() # 启动线程 producer_thread.start() consumer_thread.start() # 等待线程结束 producer_thread.join() consumer_thread.join() ``` 在这个示例代码中,我们使用了 Python 中的 Condition 类来实现线程同步和协作。在生产者线程中,如果缓冲区已满,则使用 wait() 方法等待消费者线程消费数据;在消费者线程中,如果缓冲区为空,则使用 wait() 方法等待生产者线程生产数据。当生产者线程向缓冲区中添加数据或消费者线程从缓冲区中取出数据时,需要使用 acquire() 方法获取锁,以避免竞争条件的发生。 需要注意的是,在生产者-消费者问题中,线程同步和协作是非常重要的,如果实现不当,将会导致死锁、竞争条件等问题。因此,在实际开发中,需要仔细设计和测试多线程程序,以确保程序的正确性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值