多线程---生产者与消费者模型实现


生产者与消费者模型

生产者与消费者模型------针对典型的应用场景设计的解决方案
生产者与消费者模型应用场景:有线程不断的生产数据,有线程不断地处理数据

数据的生产与数据的处理,放在同一个线程中完成,因此执行流只有一个,那么肯定是生产一个处理一个,处理完一个后才能生产。这样的话依赖关系太强----如果处理比较慢,也会拖的生产速度慢下来

因此将生产 与 处理放到不同的执行流中完成,中间增加一个数据缓冲区,做为中间的数据缓冲场所。

在这里插入图片描述
生产者与消费者模型解决的问题:

  • 解耦合
  • 支持忙闲不均
  • 支持并发

生产者与消费者模型的第一种实现:

生产者与消费者,其实只是两种业务处理的线程而已–创建线程就ok
实现的关键在于线程安全的队列
封装一个线程安全的BlockQueue-阻塞队列–向外提供线程安全的入队/出队操作

class BlockQueue
{
public:
    BlockQueue();
    ~BlockQueue();
    //编码风格:纯输入参数-const &/输出型参数 指针/输入输出型 &
    bool Push(int data);//入队数据
    bool Pop(int *data);//出队数据
private:
    std::queue<int> _queue;//非线程安全的--STL奔着性能
    int _capacity;//队列中节点的最大数量
    pthread_mutex_t  _mutex;
    pthread_cond_t  _productor_cond;
    pthread_cond_t  _customer_cond;    
};

具体代码:

#include <cstdio>
#include <iostream>
#include <queue>
#include <pthread.h>

//线程安全的阻塞队列--没有数据则消费者阻塞/数据满了则生产者阻塞

#define QUEUE_MAX 5
class BlockQueue
{
    public:
        BlockQueue(int maxq = QUEUE_MAX):_capacity(maxq){
            pthread_mutex_init(&_mutex, NULL);
            pthread_cond_init(&_pro_cond, NULL);
            pthread_cond_init(&_cus_cond, NULL);
        }
        ~BlockQueue(){
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_pro_cond);
            pthread_cond_destroy(&_cus_cond);
        }
        bool Push(int data) {
            //生产者才会入队数据,如果队列中数据满了则需要阻塞
            pthread_mutex_lock(&_mutex);
            //_queue.size() 获取队列节点个数
            while (_queue.size() == _capacity) {
                pthread_cond_wait(&_pro_cond, &_mutex);
            }
            _queue.push(data);//_queue.push() 入队操作
            pthread_mutex_unlock(&_mutex);//解锁
            pthread_cond_signal(&_cus_cond);//唤醒消费者
            return true;
        }
        bool Pop(int *data) {
            //出队都是消费者,有数据才能出队,没有数据要阻塞
            pthread_mutex_lock(&_mutex);
            //_queue.empty()  若queue为NULL则 返回true
            while (_queue.empty()) {
                pthread_cond_wait(&_cus_cond, &_mutex);
            }
            *data = _queue.front();//_queue.front() 获取队首节点数据
            _queue.pop();//出队
            pthread_mutex_unlock(&_mutex);
            pthread_cond_signal(&_pro_cond);
            return true;
        }
    private:
        std::queue<int> _queue;
        int _capacity;

        pthread_mutex_t _mutex;
        pthread_cond_t _pro_cond;
        pthread_cond_t _cus_cond;
};

void *thr_productor(void *arg) 
{
    //这个参数是我们的主线程传递过来的数据
    BlockQueue *queue = (BlockQueue*)arg;//类型强转
    int i = 0;
    while(1) {
        //生产者不断生产数据
        queue->Push(i);//通过Push接口操作queue中的成员变量
        printf("productor push data:%d\n", i++);
    }
    return NULL;
}
void *thr_customer(void *arg) 
{
    BlockQueue *queue = (BlockQueue*)arg;
    while(1) {
        //消费者不断获取数据进行处理
        int data;
        queue->Pop(&data);
        printf("customer pop data:%d\n", data);
    }
    return NULL;
}
int main()
{
    int ret, i;
    pthread_t ptid[4], ctid[4];
    BlockQueue queue;

    for (i = 0; i < 4; i++) {
        ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&queue);
        if (ret != 0) {
            printf("create productor thread error\n");
            return -1;
        }
        ret = pthread_create(&ctid[i], NULL, thr_customer, (void*)&queue);
        if (ret != 0) {
            printf("create productor thread error\n");
            return -1;
        }
    }
    for (i = 0; i < 4; i++) {
        pthread_join(ptid[i], NULL);
        pthread_join(ctid[i], NULL);
    }
    return 0;
}

在这里插入图片描述

生产者与消费者模型的第二种实现:

信号量:用于实现进程/线程间的同步与互斥
本质:一个计数器+pcb等待队列
同步:只要保证多个执行流之间对资源获取的合理性-----信号量通过计数器对资源进行计数,通过计数器判断线程是否符合访问条件
互斥:只要保证多个执行流之间对资源访问的安全性-----信号量通过不大于1的计数器,标识资源只有一个,实现同一时间只有一个线程能够访问。

使用流程:

  1. 定义信号量 sem_t
  2. 初始化信号量sem_init(sem_t ,int pshared,int val)
  3. 在访问资源之前先访问信号量判断是否符合访问条件,若不满足则阻塞:
    sem_wait(sem_t );
    sem_trywait(sem_t );
    sem_timedwait();
  4. 在促使访问资源条件满足之后,计数+1,唤醒阻塞的线程:sem_post(sem_t );
  5. 销毁信号量 sem_destroy(sem_t );
#include <cstdio>
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>

#define QUEUE_MAX 5
class RingQueue{
    public:
        RingQueue(int maxq = QUEUE_MAX):_queue(maxq), _capacity(maxq),
        _step_read(0), _step_write(0){
            //sem_init(信号量, 进程/线程标志, 信号量初值)
            sem_init(&_lock, 0, 1);//用于实现互斥锁
            sem_init(&_sem_data, 0, 0);//数据空间计数初始为0
            sem_init(&_sem_idle, 0, maxq);//空闲空间计数初始为节点个数
        }
        ~RingQueue(){
            sem_destroy(&_lock);
            sem_destroy(&_sem_data);
            sem_destroy(&_sem_idle);
        }
        bool Push(int data){
            //1. 判断是否能够访问资源,不能访问则阻塞
            sem_wait(&_sem_idle);//-空闲空间计数的判断,空闲空间计数-1
            //2. 能访问,则加锁,保护访问过程
            sem_wait(&_lock);//lock计数不大于1,当前若可以访问则-1,别人就不能访问了
            //3. 资源的访问
            _queue[_step_write] = data;
            _step_write = (_step_write + 1) % _capacity;//走到最后,从头开始
            //4. 解锁
            sem_post(&_lock);//lock计数+1,唤醒其它因为加锁阻塞的线程
            //5. 入队数据之后,数据空间计数+1,唤醒消费者
            sem_post(&_sem_data);
            return true;
        }
        bool Pop(int *data){
            sem_wait(&_sem_data);//有没有数据
            sem_wait(&_lock); // 有数据则加锁保护访问数据的过程
            *data = _queue[_step_read]; //获取数据
            _step_read = (_step_read + 1) % _capacity;
            sem_post(&_lock); // 解锁操作
            sem_post(&_sem_idle);//取出数据,则空闲空间计数+1,唤醒生产者
            return true;
        }
    private:
    std::vector<int> _queue; // 数组, vector需要初始化节点数量
    int _capacity; // 这是队列的容量
    int _step_read; // 获取数据的位置下标
    int _step_write;//写入数据的位置下标
    //这个信号量用于实现互斥
    sem_t _lock ;
    //这个信号量用于对空闲空间进行计数
    //---对于生产者来空闲空间计数>0的时候才能写数据 --- 初始为节点个数
    sem_t _sem_idle; 
    // 这个信号量用于对具有数据的空间进行计数
    // ---对于消费者来说有数据的空间计数>0的时候才能取出数据 -- 初始为0
    sem_t _sem_data; 
};


void *thr_productor(void *arg) 
{
    //这个参数是我们的主线程传递过来的数据
    RingQueue *queue = (RingQueue*)arg;//类型强转
    int i = 0;
    while(1) {
        //生产者不断生产数据
        queue->Push(i);//通过Push接口操作queue中的成员变量
        printf("productor push data:%d\n", i++);
    }
    return NULL;
}
void *thr_customer(void *arg) 
{
    RingQueue *queue = (RingQueue*)arg;
    while(1) {
        //消费者不断获取数据进行处理
        int data;
        queue->Pop(&data);
        printf("tid:%p customer pop data:%d\n", pthread_self(), data);
    }
    return NULL;
}
int main()
{
    int ret, i;
    pthread_t ptid[4], ctid[4];
    RingQueue queue;

    for (i = 0; i < 4; i++) {
        ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&queue);
        if (ret != 0) {
            printf("create productor thread error\n");
            return -1;
        }
        ret = pthread_create(&ctid[i], NULL, thr_customer, (void*)&queue);
        if (ret != 0) {
            printf("create productor thread error\n");
            return -1;
        }
    }
    for (i = 0; i < 4; i++) {
        pthread_join(ptid[i], NULL);
        pthread_join(ctid[i], NULL);
    }
    return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值