Linux多线程学习(3)--POSIX信号量及生产者消费者模型

版权声明:本文为博主原创文章,欢迎转载,转载请声明出处! https://blog.csdn.net/hansionz/article/details/84766601

一.生产者-消费者模型

1. 什么是生产者-消费者模型

在线程的同步和互斥中,有一个生产者-消费者模型的经典问题,它也称为有界缓冲区。两个进程共享一个公共的固定大小的缓冲区。其中一个是生产者,将信息放入缓冲区,另一个是消费者,从缓冲区中取信息。既一个或多个生产者(线程或进程)创建着一些数据放入缓冲区,然后这些数据由一个或多个消费者(线程或进程)处理,从缓冲区拿走。数据在生产者和消费者之间可以通过某种IPC传递。

2.生产者-消费者模型的三种关系

在这里插入图片描述

  • 生产者-生产者:互斥
  • 生产者-消费者:同步和互斥
  • 消费者-消费者:互斥

3.基于BlockQueue实现生产者-消费者模型

生产者-消费者模型问题的关键在于缓冲区已满,而此时生产者还想往其中放入一个新的数据的情况。其解决办法是让生产者睡眠,待消费者从缓冲区中取出一个或多个数据时再唤醒它。同样的, 当消费者试图从缓冲区中取数据而发现缓冲区空时,消费者就睡眠,直到生产者向其中放一些数据后再将其唤醒

//cp.hpp
#ifndef __CP_HPP__
#define __CP_HPP__ 

#include <iostream>
#include <queue>
#include <mutex>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <pthread.h>

using namespace std;

template<class T>
class BlockQueue{

private:
  //给队列上锁
  void LockQueue()
  {
    pthread_mutex_lock(&lock);
  }
  //解锁队列
  void UnlockQueue()
  {
    pthread_mutex_unlock(&lock);
  }
  
  //判断队列是否满
  bool IsFull()
  {
    return q.size() == (unsigned int)cap ? true : false;
  }
  //判断杜烈是否为空
  bool IsEmpty()
  {
    return q.size() == 0 ? true : false;
  }
  //生产者在条件变量下阻塞等待
  void ProductWait()
  {
    pthread_cond_wait(&p_cond, &lock);
  }
  //消费者在条件变量下阻塞等待
  void ConsumeWait()
  {
    pthread_cond_wait(&c_cond, &lock);
  }
  //使生产者的条件得到满足,唤醒该线程
  void SignalProduct()
  {
    pthread_cond_signal(&p_cond);
  }
  //使消费者的条件得到满足,唤醒该线程
  void SignalConsume()
  {
    pthread_cond_signal(&c_cond);
  }
  //判断是否达到高水位线(达到2/3队列的长度既通知消费者来消费)
  bool IsHighLine()
  {
    return q.size() > (unsigned int)high_line ? true : false;
  }
  //判断是否达到低水位线(低于到1/3队列的长度既通知生产者来消费)
  bool IsLowLine()
  {
    return q.size() < (unsigned int)low_line ? true : false;
  }
public:
  //构造函数
  BlockQueue(const int& _cap)
    :cap(_cap)
    ,high_line((_cap*2)/3)
    ,low_line((_cap*1)/3)
  {
    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&p_cond, NULL);
    pthread_cond_init(&c_cond, NULL);
  }
  //生产者向缓冲区放数据(同步与互斥)
  void PushData(int& data)
  {
    LockQueue();//互斥锁实现互斥

    while(IsFull())//使用while进行二次判断,防止假唤醒
    {
      ProductWait();
    }
    //高于水位线则通知消费者来消费
    if(IsHighLine())
    {
      SignalConsume();//条件变量实现同步
    }
    q.push(data);

    UnlockQueue();
  }
  void PopData(int& data)//输出型参数,通过参数带回返回值
  {
     LockQueue();
     //使用while进行二次判断,防止假唤醒
     while(IsEmpty())
     {
       ConsumeWait();
     }
     //低于水位线则通知生产者来生成
     if(IsLowLine())
     {
       SignalProduct();
     }
     data = q.front();
     q.pop();

     UnlockQueue();
  }
  //析构函数,销毁互斥量和条件变量
  ~BlockQueue()
  {
    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&p_cond);
    pthread_cond_destroy(&c_cond);
  }
private:
  queue<T> q;
  int cap;//阻塞队列的最大长度
  
  int high_line;//高水位线
  int low_line;//低水位线
  pthread_mutex_t lock;
  pthread_cond_t p_cond;//队空,生产者生产
  pthread_cond_t c_cond;//队满,消费者消费
};
#endif //__CP_HPP__
//cp.cc
#include "cp.hpp"

const int num = 10;//阻塞队列的最大长度

void *consume_routine(void *arg)
{
  BlockQueue<int>* pbq = (BlockQueue<int>*)arg;
  int data;
  for(;;)
  {
    pbq -> PopData(data);
    cout << "consume done, data is " << data << endl;
    sleep(1);
  }
}
void *product_routine(void *arg)
{
  BlockQueue<int>* pbq = (BlockQueue<int>*)arg;
  srand((unsigned long)time(NULL));
  for(;;)
  {
    int data = rand() % 100 + 1;
    pbq -> PushData(data);
    cout << "product done,data is " << data << endl;
    sleep(1);
  }
}
int main()
{
  BlockQueue<int> *pbq = new BlockQueue<int>(num);
  pthread_t p,c;//product(生产者)、consume(消费者)

  pthread_create(&c, NULL, consume_routine, (void*)pbq);
  pthread_create(&p, NULL, product_routine, (void*)pbq);
	
  //等待线程终止
  pthread_join(p, NULL);
  pthread_join(c, NULL);
  //释放开辟在栈上的队列
  delete pbq;
  return 0;
}

上边的阻塞队列实现的生产者-消费者模型只实现了生产者-消费者之间的同步与互斥关系,如果要实现生产者和生产者、消费者和消费者之间的互斥关系,还应该增加一些生产者和消费者,并且在生产和消费的时候加上互斥锁,即可实现他们之间的互斥关系。
注:上述问题的实现代码位于:https://github.com/hansionz/Linux_Code/tree/master/pthread/cp

二.POSIX信号量

1.什么是POSIX信号量

在学习进程间通信的时候,接触学习过SystemV版本的信号量,一个二元信号量就相当于一把互斥锁。而POSIX信号量和SystemV信号量作用是相同的,都是用来实现进程和线程间同步操作的,从而可以达到无冲突的共享资源的目的。它们两个的区别在于SystemV版本的信号量在内核中维护,而POSIX信号量存放在共享内存区。

2.初始化信号量

信号量是一个sem_t类型的变量,要使用POSIX信号量必须引入头文件#include <semaphore.h>,在使用之前一定要初始化。

int sem_init(sem_t *sem, int pshared, unsigned int value); 
参数:    
	pshared:0表⽰线程间共享,非0表示进程间共享    
	value:信号量初始值

3. 销毁信号量

int sem_destroy(sem_t *sem);
参数: 
	sem:代表要销毁信号的地址

4. 等待信号量

等待信号量相对SystemV版本信号量的P操作

功能:等待信号量,会将信号量的值减1 
int sem_wait(sem_t *sem);

5.发布信号量

发布信号量相对于SystemV版本信号量的V操作

功能:发布信号量,表示资源使⽤用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);

6.基于POSIX信号量实现的环形队列模拟的生产者-消费者模型

在这里插入图片描述

//cp.hpp
#ifndef __CP_HPP__
#define __CP_HPP__ 

#include <iostream>
#include <queue>
#include <mutex>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

using namespace std;

template<class T>
class RingQueue{
private:
 //等待信号量,信号量减1
 void P(sem_t& sem)
 {
   sem_wait(&sem);
 }
 //发布信号量,信号量加1
 void V(sem_t& sem)
 {
   sem_post(&sem);
 }
public:
  //构造函数,在构造中必须先将vector初始化一定长度代表空格信号量的个数
  RingQueue(int cap)
    :_cap(cap)
    ,ring(cap)
  {
    c_step = p_step = 0;
    sem_init(&blank_sem, 0, _cap);
    //数据信号量初始为0
    sem_init(&data_sem, 0, 0);
  }
  //向队列中入一个数据,要保证同步与互斥
  void PushData(const T& data)
  {
    P(blank_sem);

    ring[p_step] = data;
    p_step++;
    p_step %= _cap;

    V(data_sem);
  }
  //从循环队列出一个数据
  void PopData(int& data)
  {
    P(data_sem);

    data = ring[c_step];
    c_step++;
    c_step %= _cap;

    V(blank_sem);
  }
  ~RingQueue()
  {
    sem_destroy(&blank_sem);
    sem_destroy(&data_sem);
  }
private:
  vector<T> ring; //vector模拟实现环形队列
  int _cap; //队列的长度 

  sem_t blank_sem;
  sem_t data_sem;

  int c_step;//消费者步伐
  int p_step;//生产者步伐
};

#endif //__CP_HPP__
//cp.cc
#include "cp.hpp"
const int num = 10; //空格信号量个数

void *consume_routine(void *prq)
{
  RingQueue<int>* q = (RingQueue<int>*)prq;
  int data;
  for(;;)
  {
    q -> PopData(data);
    cout << "consume done,data is:" << data << endl;
  }
}
void *product_routine(void *prq)
{
  RingQueue<int>* q = (RingQueue<int>*)prq;
  srand((unsigned long)time(NULL));
  for(;;)
  {
    int data = rand() % 100 + 1;
    q -> PushData(data);
    cout << "product done,data is:" << data << endl;
    sleep(1);
  }
}
int main()
{
  RingQueue<int>* prq = new RingQueue<int>(num);
  pthread_t p,c;

  pthread_create(&p, NULL, product_routine, (void*)prq);
  pthread_create(&c, NULL, consume_routine, (void*)prq);

  pthread_join(p, NULL);
  pthread_join(c, NULL);

  delete prq;
  prq = nullptr;

  return 0;
}

注:代码于:https://github.com/hansionz/Linux_Code/tree/master/pthread/ring_cp

猜你喜欢

转载自blog.csdn.net/hansionz/article/details/84766601