生产者与消费者模型&信号量

6.生产者与消费者模型

  • 6.1 123规则
    • 1个线程安全的队列(只要保证先进先出的特性的数据结构都可以称之为队列)
      • 该队列需要保证互斥,即同一时刻只能有一个线程对该安全队列进行操作
      • 还需要保证同步,即生产者往安全队列生产一个后,就要通知消费者来消费,同样消费者从队列中消费一个后,就要通知生产者来生产
    • 2种角色的线程:生产者&消费者
      • 生产者:往安全队列中生产
      • 消费者,从安全队列中拿出元素进行消费
    • 3个规则
      • 生产者与生产者互斥
      • 消费者与消费者互斥
      • 生产者与消费者互斥+同步
  • 6.2应用场景
    • 后端服务器常见的场景:消息的接收和转发
  • 6.3优点
    • 忙闲不均:在某一时刻可能接收消息的线程不忙而处理消息的线程一直处于工作状态。
    • 生产者与消费者解耦:生产者只负责生产,消费者只负责消费,生产者生产时不会关心消费者消费,消费者消费时不会关心生产者生产,互相不干扰
      • 生产者与消费者不解耦的例子:

    • 支持高并发:同一时刻可以有多个用户向生产者发送消息
  • 代码模拟实现
#include<iostream>
#include<queue>
using namespace std;
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>

#define THREAD_COUNT 2  

int g_data=0;

//1.线程安全队列
class SafeQueue{
    public:
        SafeQueue()
            :_capacity(10)
        {
            pthread_mutex_init(&_que_lock,NULL);
            pthread_cond_init(&_prod_cond,NULL);
            pthread_cond_init(&_cons_cond,NULL);
        }
        ~SafeQueue(){
           pthread_mutex_destroy(&_que_lock);
           pthread_cond_destroy(&_prod_cond);
           pthread_cond_destroy(&_cons_cond);
        }
        
        //提供给生产者生产的接口
        void Push(int data){
            pthread_mutex_lock(&_que_lock);
            while(_que.size() >= _capacity){
                pthread_cond_wait(&_prod_cond,&_que_lock);
            }
            _que.push(data);
            printf("I am product thread:%p,i product %d\n",pthread_self(),g_data);
            pthread_mutex_unlock(&_que_lock);
            pthread_cond_signal(&_cons_cond);
        }

        //提供给消费者消费的接口
        void Pop(int* data){
            pthread_mutex_lock(&_que_lock);
            while(_que.empty()){
                pthread_cond_wait(&_cons_cond,&_que_lock);
            }
           *data=_que.front();
           _que.pop();
           printf("I am consume thread:%p,i consume %d\n",pthread_self(),*data);
           pthread_mutex_unlock(&_que_lock);
           pthread_cond_signal(&_prod_cond);
        }
    private:
        //队列
        queue<int> _que;
        //互斥锁
        pthread_mutex_t _que_lock;
        //同步
        //生者者对应的条件变量
        pthread_cond_t  _prod_cond;
        //消费者对应的条件变量
        pthread_cond_t  _cons_cond;
        //固定队列的容量
        size_t _capacity;
};

pthread_mutex_t g_lock= PTHREAD_MUTEX_INITIALIZER;

void* prod_start(void* arg){
    SafeQueue* sq = (SafeQueue*)arg;
    while(1){
        pthread_mutex_lock(&g_lock);
        sq->Push(g_data);
        g_data++;
        //sleep(1);
        pthread_mutex_unlock(&g_lock);
    }
}

void* cons_start(void* arg){
    SafeQueue* sq = (SafeQueue*)arg;
    while(1){
        int data;
        sq->Pop(&data);
    }
}

int main(){
    SafeQueue* sq=new SafeQueue();
    pthread_t prod[THREAD_COUNT],cons[THREAD_COUNT];
    for(int i=0;i<THREAD_COUNT;i++){
        int ret = pthread_create(&prod[i],NULL,prod_start,(void*)sq);
        if(ret < 0){
            perror("pthread_create:");
            return 0;
        }
        ret = pthread_create(&cons[i],NULL,cons_start,(void*)sq);
        if(ret < 0){
            perror("pthread_create:");
            return 0;
        }
    }

    for(int i=0;i<THREAD_COUNT;i++){
        pthread_join(prod[i],NULL);
        pthread_join(cons[i],NULL);
    }
    delete sq;
    return 0;
}

7.信号量

  • 7.1信号量的原理
    • 资源计数器+PCB等待队列
    • 资源技术器:
      • 执行流获取信号量,
        • 获取成功,信号量计数器减1操作
        • 获取失败,执行流放入到PCB等待队列
      • 执行流释放信号量成功之后,计数器加1操作
  • 7.2信号量的接口
    • 初始化接口
      • int sem_init(sem_t *sem,int pshared,unsigned int value);
        • sem :信号量,sem_t是信号量的类型
        • pshared :该信号量是用于线程间还是用于进程间
          • 0:用于线程间,将信号量设置为全局变量
          • 非0:用于进程间,将信号量所用到的资源在共享内存当中进行开辟
        • value :资源的个数,初始化信号量计数器的
    • 等待接口:
      • int sem_wait(sem_t * sem) ;
        • 1.对资源计数器进行减1操作(减1操作称为P操作)
        • 2.判断资源汁数器的值是否小于0.
          • 是:则阻塞等待,将执行流放到PCB等待队列当中
          • 否:则接口返回
    • 释放接口:
      • int sem_post (sem_t* sem);
        • 1.会对资源计数器进行加1操作(加1操作称为V操作)
        • 2.判断资源计数器的值是否小于等于0
          • 是:通知PCB等待队列,说明计数器加1操作之前,计数器的值是一个负数,说明有线程在PCB等待队列中等待
          • 否:不用通知PCB等待队列,因为没有线程在等待
    • 销毁接口:
      • int sem_destroy (sem_t *sem) ;
  • 先获取信号量,在获取互斥锁!
    • 先获取信号量,在保证互斥(互斥锁,信号量)
    • 如果先获取互斥锁,再获取信号量,会存在这样一种场景,某个线程获取到互斥锁后,在获取信号量时,资源计数器进行减1操作后发现当前资源不可用,则线程就会被放到PCB等待队列,互斥锁也被带走了,其他线程就无法获取到互斥锁,就不能再往下推进了。
    • 信号量版生产者与消费者模型
      • 生产者消耗自己的资源,给消费者增加资源
      • 消费者消费自己的资源,给生产者增加资源
  • 用信号量实现互斥
  • (信号量)代码模拟:
  • #include<stdio.h>
    #include<iostream>
    #include<vector>
    #include<pthread.h>
    #include<semaphore.h>
    #include<unistd.h>
    
    using namespace std;
    /*1.线程安全队列
     * 用环形队列(数组)
     * 线程安全:
     * 互斥,同步
     *
     * */
    #define CAPACITY 1
    
    class RingQueue{
        public:
            RingQueue()
            :v_(CAPACITY)
            {
                capacity_=CAPACITY;
                sem_init(&sem_lock_,0,1);
                sem_init(&sem_prod_,0,capacity_);
                sem_init(&sem_cons_,0,0);
                pos_write=0;
                pos_read=0;
            }
    
            ~RingQueue(){
                sem_destroy(&sem_lock_);
                sem_destroy(&sem_prod_);
                sem_destroy(&sem_cons_);
            }
    
            void Push(int data){
                sem_wait(&sem_prod_);//生产者获取生产者信号量
                sem_wait(&sem_lock_);//加锁
                printf("i am product:%p,i product%d\n",pthread_self(),data);
                v_[pos_write]=data;
                pos_write=(pos_write+1)%capacity_;
                sem_post(&sem_lock_);//解锁
                sem_post(&sem_cons_);//生产者通知消费者的PCB等待队列
            }
    
            void Pop(){
                sem_wait(&sem_cons_);//消费者获取自己的信号量
                sem_wait(&sem_lock_);//加锁
                int data=v_[pos_read];
                pos_read=(pos_read+1)%capacity_;
                printf("I am consume thread:%p,i consume %d\n",pthread_self(),data);
                sem_post(&sem_lock_);//解锁
                sem_post(&sem_prod_);//消费者通知生产者的PCB等待队列
            }
        private:
            vector<int> v_;
            //数组容量大小
            size_t capacity_;
            //保证互斥的信号量
            sem_t sem_lock_;
            //保证同步的信号量
            sem_t sem_prod_;
            sem_t sem_cons_;
    
            int pos_write;
            int pos_read;
    };
    
    #define THREADCOUNT 1
    int g_data=0;
    
    void* prod_start(void* arg){
        RingQueue* rq=(RingQueue*)arg;
        //sleep(5);//等5秒再开始生产
        while(1){
            rq->Push(g_data);
            g_data++;
            sleep(1);//每隔一秒生产一个
        }
    }
    
    void* cons_start(void* arg){
        RingQueue* rq=(RingQueue*)arg;
        while(1){
            rq->Pop();
            sleep(2);//每隔2秒消费一个
        }
    
    }
    /*创建生产者线程和消费者线程
     * */
    int main(){
        RingQueue* rq=new RingQueue();
        pthread_t cons[THREADCOUNT],prod[THREADCOUNT];
        for(int i=0;i<THREADCOUNT;i++){
            int ret=pthread_create(&prod[i],NULL,prod_start,(void*)rq);
            if(ret < 0){
                perror("pthread_create:");
                return 0;
            }
            ret=pthread_create(&cons[i],NULL,cons_start,(void*)rq);
            if(ret < 0){
                perror("pthread_create:");
                return 0;
            }
        }
    
        for(int i=0;i<THREADCOUNT;i++){
            pthread_join(prod[i],NULL);
            pthread_join(cons[i],NULL);
        }
        delete rq;
        return 0;
    }

猜你喜欢

转载自blog.csdn.net/sy2453/article/details/124694794