基于Posix信号量实现的环形生产者消费者模型

基于Posix信号量实现的环形生产者消费者模型

  • posix信号量
  • 操作接口
    • 定义
    • 初始化接口
    • 等待接口
    • 唤醒接口
    • 销毁接口
  • 如何保证同步&互斥
  • 基于Posix信号量实现的环形生产者消费者模型
    • 使用的数据结构
    • 实现操作
  • 代码实现
    • 环形队列类
    • 消费者线程执行的逻辑
    • 生产者线程执行的逻辑
    • 主函数
    • 运行结果

posix信号量

  • 作用

可以完成线程间与进程间的同步与互斥

  • 本质
资源计数器 + PCB等待队列 + 提供等待和唤醒的接口

在条件变量中,我们了解到条件变量是实现线程间同步功能的一种方式。而posix信号量和条件变量相比,多了一个资源计数器。
资源计数器的作用就是用来对临界资源进行比较,posix信号量通过判断自身的资源计数器的情况,来得到当前资源是否可用的信息:

  • 可用:则对临界资源进行访问
  • 不可用:则进行阻塞等待,直到被唤醒接口唤醒。
    基于Posix信号量实现的环形生产者消费者模型_第1张图片

操作接口

定义

sem_t 

eg:sem_t sem;

初始化接口

 #include 
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • sem:传入信号量的地址
  • pshared:表示当前信号量表示的内容是线程间还是进程间
    0 --》 表示线程间
    1 --》 表示进程间

当使用sem_init()函数初始化信号量为进程间的时候,会在共享内存中开辟一段共享内存,用来保存信号量的数据结构,其中包括资源计数器和PCB等待队列。所以调用唤醒或者等待接口的本质就是通过操作共享内存实现了不同进程之间的通信,进而实现不同进程间的同步与互斥。
而在线程之间,一般就把这个信号量设置为全局变量或者类的私有成员变量,就可以通过操作这个变量来实现不同线程之间的同步和互斥。

  • value:实际的资源数量,用于初始化信号量当中的资源计数器,表示当前一共有value个资源

等待接口

#include 
//阻塞等待
int sem_wait(sem_t *sem);
//非阻塞等待
int sem_trywait(sem_t *sem);
//带有时间的等待
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
  • 如果判断当前信号量资源计数器中的值大于0,则能够成功获取信号量;如果资源计数器小于等于0,就说明没有资源,则阻塞该线程

唤醒接口

#include 
int sem_post(sem_t *sem);
  • 作用:用来发布信号量,告诉程序当前资源使用完成,需要归还资源或者让生产者重新生产一个资源,并对信号量中的资源计数器进行+1操作,然后唤醒PCB等待队列中的线程。

销毁接口

#include 
int sem_destroy(sem_t *sem);

如何保证同步&互斥

  • 同步
    在初始化工作的时候,根据我们制定的资源的数量来初始化posix信号量中的资源计数器
    基于Posix信号量实现的环形生产者消费者模型_第2张图片
  • 互斥
    互斥的本质就是让资源计数器只有两个取值,一个是可以用,一个是不可以用。
    所以我们就可以初始化信号量中的资源计数器的值为1,这样就保证了1为资源可以用,0为资源不可以用。

基于Posix信号量实现的环形生产者消费者模型_第3张图片

基于Posix信号量实现的环形生产者消费者模型

使用的数据结构

一个用数组模拟的环形队列
这个队列需要满足先进先出的特性

实现操作

  • 前后指针,一个是生产者指针,一个是消费者指针。当两个指针指向同一位置的时候,表示表示当前队列为空;而当生产者指针的下一个位置 = 消费者指针的位置时,表示队列已满。

基于Posix信号量实现的环形生产者消费者模型_第4张图片

  • 计算下一个位置的方式
(当前位置 + 1) % 数组容量
  • 同步
    在生产者生产一个资源后,就通知消费者可以去消费了
生产:
sem_init(&_SemProducer,0,数组容量);//初始化
sem_wait(&_SemProducer);//如果队列满了,资源没有消耗
arr[] = ?;//生产资源
sem_post(&_SemConsumer);//通知消费
消费:
sem_init(&_SemConsumer,0,0);//初始化
sem_wait(&_SemConsumer);//如果队列为空,没有资源
*Data = arr[];//消费资源
sem_post(&_SemProducerr);//通知生产
  • 互斥
sem_init(&_Lock,0,1);

只设置一个临界资源,让临界资源同时只能被一个线程访问。

代码实现

环形队列类

class LoopQueue
{
public:
  LoopQueue()
    : _arr(SIZE)
  {
    _Producer = 0;
    _Consumer = 0;
    _Capacity = SIZE;
    //同步生产者,信号量计数器的值和数组的容量一样大
    sem_init(&_SemProducter,0,_Capacity);
    //同步消费者,一开始并没有一个有效元素,所以初始化为0,
    sem_init(&_SemConsumer,0,0);

    //互斥,初始化资源数为1,只能有一个线程同时访问资源
    sem_init(&_Lock,0,1);
  }

  //操作生产者 
  void Push(int& Data)
  {
    sem_wait(&_SemProducter);//获取当前信号量到生产者这里,如果拿不到就阻塞等待 
	//到这里说明,生产者需要生产资源了 
	
    sem_wait(&_Lock);
    _arr[_Producer] = Data;
    _Producer = (_Producer + 1) % _Capacity;
    sem_post(&_Lock);
    
    sem_post(&_SemConsumer);//通知消费者消费 
  }
  
  //操作消费者 
  void Pop(int* Data)
  {
    sem_wait(&_SemConsumer);//获取当前信号量到消费者这里,如果拿不到就阻塞等待 
	//到这里说明,消费者可以消费资源了
	 
    sem_wait(&_Lock);
    *Data = _arr[_Consumer];
    _Consumer = (_Consumer + 1) % _Capacity;
    sem_post(&_Lock);

    sem_post(&_SemProducter);//消费完毕,通知生产者生产 
  }

  ~LoopQueue()
  {
    sem_destroy(&_SemConsumer);
    sem_destroy(&_SemProducter);
    sem_destroy(&_Lock);
  }

private:
  vector<int> _arr;
  size_t _Capacity;
  // Double pointer
  int _Consumer;
  int _Producer;

  //同步
  sem_t _SemProducter;
  sem_t _SemConsumer;

  //互斥 
  sem_t _Lock;
};

消费者线程执行的逻辑

void* Consumer_start(void* arg)
{
  LoopQueue* que = (LoopQueue*) arg;

  int Data;
  while(1)
  {
    que->Pop(&Data);
    printf("_Consumer [%p]--> [%d]\n",pthread_self(),Data);
  }
  return NULL;
}

生产者线程执行的逻辑

void* Producer_start(void* arg)
{
  LoopQueue* que = (LoopQueue*) arg;
  int Data = 1;
  while(1)
  {
    que->Push(Data);
    printf("_Producer [%p] --> [%d]\n",pthread_self(),Data);
    Data++;
  }
  return NULL;
}

主函数

int main()
{
  LoopQueue* que = new LoopQueue();
  pthread_t com_tid[THREADCOUNT],pro_tid[THREADCOUNT];

  for(int i = 0; i < THREADCOUNT; i++)
  {
    int ret = pthread_create(&com_tid[i],NULL,Consumer_start,(void*)que);
    if(ret < 0)
    {
      perror("pthread_create consumer");
      return 0;
    }

    ret = pthread_create(&pro_tid[i],NULL,Producer_start,(void*)que);
    if(ret < 0)
    {
      perror("pthread_create Producter");
      return 0;
    }
  }

  for(int i = 0; i < THREADCOUNT; i++)
  {
    pthread_join(com_tid[i],NULL);
    pthread_join(pro_tid[i],NULL);
  }

  delete que;
  que = NULL;

  return 0;
}

运行结果

基于Posix信号量实现的环形生产者消费者模型_第5张图片

  • 源码地址

https://github.com/duchenlong/linux-text/blob/master/thread/semqueue.cpp

你可能感兴趣的:(操作系统,Posix信号量,生产者与消费者)