生产者消费者模型

生产者消费者模型,它是一个多线程场景中的典型应用,应用广泛,完成某些操作的时候,需要一些数据,这样的数据可能由专门的线程/进程产生,在由专门的线程/进程使用。
生产者消费者模型,它需要一个交易场所(存储数据的地方,可能是一个队列,也可能是一个栈,或者其他数据结构)。生产者负责生产数据,把数据放到交易场所中,消费者负责消费数据,把数据冲交易场所中取出。

生产者与生产者之间是互斥关系。
消费者与消费者之间是互斥关系。
生产者与消费者之间是同步和互斥关系。

首先我们只有互斥锁来模拟这个创景

#include<stdio.h>   
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<vector>
using namespace std;   //只加上了互斥锁,保证了生产者之间,消费者之间的互斥
//实现一个生产者消费者模型
//首先要有一个交易场所
vector<int> data;//全局
pthread_mutex_t mt;
void* Product(void* arg)
{
    (void)arg;
    //负责把数据往交易场所取放
    int count=0;
    while(1)
    {
        pthread_mutex_lock(&mt);
        data.push_back(++count);
        pthread_mutex_unlock(&mt);
        usleep(789789);
    }
    return NULL;
}

void* Consum(void* arg)
{
    (void)arg;
    //负责把交易场所中的数据取出来
    while(1)
    {
        //每次取最后一个元素
        pthread_mutex_lock(&mt);
        if(!data.empty())
        {
            int result=data.back();//如果为空内存访问越界
            data.pop_back();
            printf("result=%d\n",result);
        }
        pthread_mutex_unlock(&mt);
        usleep(123123);
    }
    return NULL;
}

int main()
{
    pthread_mutex_init(&mt,NULL);
    pthread_t tid1,tid2,tid3,tid4;
    pthread_create(&tid1,NULL,Product,NULL);
    pthread_create(&tid2,NULL,Product,NULL);
    pthread_create(&tid3,NULL,Consum,NULL);
    pthread_create(&tid4,NULL,Consum,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);
    pthread_join(tid4,NULL);
    pthread_mutex_destroy(&mt);
    return 0;
}

这份代码只是保证了生产者与生产者之间的互斥关系,消费者与消费者之间的互斥关系。没有保证生产者与消费者之间的同步,可能会出现线程饿死。为了解决这个问题,我们加上条件变量。

#include<iostream>  //增加同步!!!
#include<unistd.h>
#include<pthread.h>
#include<vector>
using namespace std;
//实现一个生产者消费者模型
//首先要有一个交易场所
vector<int> data;//全局
pthread_mutex_t mt;
pthread_cond_t cond;
void* Product(void* arg)
{
    (void)arg;
    //负责把数据往交易场所取放
    int count=0;
    while(1)
    {
        pthread_mutex_lock(&mt);
        data.push_back(++count);
        pthread_mutex_unlock(&mt);
        pthread_cond_signal(&cond);
        usleep(789789);
    }
    return NULL;
}

void* Consum(void* arg)
{
    (void)arg;
    //负责把交易场所中的数据取出来
    while(1)
    {
        //每次取最后一个元素
        pthread_mutex_lock(&mt);
        //这个代码最好写出while 而不是if
        //pthread_cond_wait 返回不一定就是其他线程signal
        //这个函数也可能被信号打断!
        while(data.empty())
        {
            //1.先释放锁
            //2.等待条件就绪(有其他线程调用pthrad_cond_signal)
            //3.如果条件就绪了,重新获取锁
            //1,2必须是原子操作
            //加上了wait之后最大的意义在于如果没有数据,消费者线程
            //不必进行空转,节省了资源
            
            //没数据,就会在这个等!!!
            pthread_cond_wait(&cond,&mt);
        }
        int result=data.back();//如果为空内存访问越界
        data.pop_back();
        printf("result=%d\n",result);
        pthread_mutex_unlock(&mt);
        usleep(123123);
    }
    return NULL;
}

int main()
{
    pthread_cond_init(&cond,NULL);
    pthread_mutex_init(&mt,NULL);
    pthread_t tid1,tid2,tid3,tid4;
    pthread_create(&tid1,NULL,Product,NULL);
    pthread_create(&tid2,NULL,Product,NULL);
    pthread_create(&tid3,NULL,Consum,NULL);
    pthread_create(&tid4,NULL,Consum,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);
    pthread_join(tid4,NULL);
    pthread_mutex_destroy(&mt);
    pthread_cond_destroy(&cond);
    return 0;
}

增加同步的意义是,避免了消费者频繁的空转,防止线程饥饿,减少调度的开销。
同步与互斥不一定非得用互斥锁和条件变量来实现,可以用信号量来模拟一个阻塞队列BlockingQueue来完成。

信号量本质是一个计数器,表示可用资源的个数。

//初始化信号量
int sem_init();
//销毁信号量
int sem_destroy()
//等待信号量
int sem_wait()
//发布信号量
int sem_post()

使用信号量时,sem_wait()操作用来申请资源,计数器-1,sem_post()操作来释放资源,计数器+1,当计算器为0时,再去操作就会发生阻塞。信号量这个计算操作是原子的。

//hpp 声明与实现在一起用hpp
#pragma once
///实现阻塞队列的代码!!!!!!!
#include<semaphore.h>
#include<unistd.h>
#include<cstdio>
#include<pthread.h>
#include<vector>


//阻塞队列:一种常见的数据结构,线程安全版本的队列
//一般Blockingqueue,都要求长度有上限
//如果队列为空,去执行pop会阻塞
//如果队列满了,去执行push也会阻塞

//信号量表示互斥比较简单,但是表示同步比较复杂

//信号量表示可用资源的个数
//一个信号量表示当前队列中元素的个数
//另一个信号量表示当前队列中空格的个数

//插入元素,消耗空格资源,释放元素资源,
//删除元素就是消耗了一个元素资源,释放了一个空格资源

//如果用信号量表示互斥,p操作和v操作在同一个函数
///如果用信号量表示同步,p操作和v操作在不同的函数中


//信号量这个计算器的+1 -1是原子的
template<typename T>
class BlockingQueue
{
public:
    BlockingQueue(int max_size)
    :max_size_(max_size)
    ,head_(0)
    ,tail_(0)
    ,size_(0)
    ,queue_(max_size)
    {
        sem_init(&lock_,0,1);//可用资源为1个 第二个参数为0 ,表示只在当前进程中,
        sem_init(&elem_,0,0);
        sem_init(&blank_,0,max_size);
    }
    ~BlockingQueue()
    {
        sem_destroy(&lock_);
        sem_destroy(&elem_);
        sem_destroy(&blank_);
    }
    void Push(const T& data)
    {
        //每次要插入元素,就得先申请一个空格资源
        //如果没有空格资源 (信号量为0),说明队列满了;
        //满了就不能在继续插入,并且在push中阻塞
        sem_wait(&blank_);//申请一个空格资源!!!!!!


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

        sem_post(&elem_);//
    }
    //data表示出队列的这个元素
    void Pop(T* data)  //输出型参数
    {
        //每次出队列 先申请一个元素资源
        //如果没有元素资源,队列为空
        //就不能直接出队列,而需要在pop处位置阻塞
        sem_wait(&elem_);

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

        sem_post(&blank_);///释放空格
    }
private:
    std::vector<T> queue_;
    int head_;
    int tail_;
    int size_;
    int max_size_;
    sem_t lock_;//可以用二元信号量表示互斥锁  (非0即1)表示互斥锁
    sem_t elem_;//可用元素个数
    sem_t blank_;//可用空格个数
};
///用BlockingQueue实现一个简单的生产者消费者模型
 
 #include"BlockingQueue.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",count);
      //  usleep(123123);
    }
    return NULL;
}

//进程间通信 管道 本质就是一个BlockingQueue
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值