Linux多线程_(Posix信号量实现环形队列生产者消费者模型)

Posted 楠c

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux多线程_(Posix信号量实现环形队列生产者消费者模型)相关的知识,希望对你有一定的参考价值。

1. 信号量

1.1 是什么

信号量也叫做信号灯,本质是一个计数器。
在这里插入图片描述
因为互不影响,所以多个线程可以同时进入。但是我们害怕控制不了进程的个数。
所以引入信号量,相当于一个监测机制。
count-- (p操作)
count++ (v操作)

所有线程都会看见他,信号量也是临界资源,所以他必须是原子操作。count–,count++并不是原子操作,所以要给他加把锁,count相当于信号量的值(value)个数。
例如

p()
{
  lock();
  if(cout>0)
  {
  cout--;
  }
  else
  {
   wait();
  }
  unlock
}

1.2 为什么

因为临界资源被我们分成了多份,多线程并发进入并不冲突,提高效率。

1.3 怎么用

1.3.1 初始化信号量

在这里插入图片描述

1.3.2 销毁信号量

在这里插入图片描述

1.3.3 等待信号量

在这里插入图片描述

1.3.4 发布信号量

在这里插入图片描述

2. 基于环形队列实现生产者消费者模型

在之前环形队列使用一个数组模拟的,由于队列为空和为满两种情况,都是head==tail。所以让数组多开一个位置。

具体讲解:
用数组设计一个环形队列

还有一种方法用计数器模拟,信号量就是一个天然的计数器。

编辑代码之前,先设计一下整个框架。

消费者最关心的就是数组里的数据。而生产者最关心的就是数组下标,也就是那个格子。需要两个信号量来管控。但是要让着两个信号量的值协同起来。

在这里插入图片描述
通俗一点说

生产者,生产一个数据,消耗一个格子
消费者,消费一个数据,增加一个格子

那么就引入4个问题。

  1. 假如生产苹果,生产者生产5个,消费者消费了2个,生产者继续生产2个,他不能继续生产第三个,因为人家还没消费,你在生产就覆盖掉了。

  2. 假如消费苹果,生产者生产5个,消费者消费了5个,生产者还没生产,你不能继续消费,因为人家还没生产。

  3. 当两者处于同一位置,为空条件,消费者不能运行。

  4. 当两者处于同一位置,为满条件,生产者不能运行。

信号量将这些问题全部解决。

  1. 生产5个,格子资源为0,数据资源为5,消费两个,格子资源为2,数据资源为3,生产两个,格子资源为0,数据资源为5。由于没有格子资源,所以不会再生产。
  2. 生产5个,格子资源为0,数据资源为5,消费5个,格子资源为5,数据资源为0, 由于没有数据资源,所以不会在消费。
  3. 处于同一位置,为空,消费者不会运行,因为数据资源为0。
  4. 处于同一位置,为满,生产者不会运行,因为格子资源为0。

这个临界区分成多个部分,由数据资源和格子资源组成。两个线程同时进来大多数时间访问的是不同区域,并发执行,即使访问了同一块区域,由于信号量的设定,此时只会有一个线程执行。意味着永远不会互相影响。

2.1 代码实现

CircleQueue.hpp

#pragma once
#include<iostream>
#include<vector>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#include<unistd.h>
using namespace std;
#define NUM 10

class CircleQueue{
  private: 
    vector<int> v;
    sem_t sem_blank;
    sem_t sem_data;
    int max_cap;
    int c_index;
    int p_index;
  public:
    CircleQueue(int cap=NUM)
     :v(cap)
     ,max_cap(cap)
     ,c_index(0)
     ,p_index(0)
    {
      sem_init(&sem_blank,0,max_cap);
      sem_init(&sem_data,0,0);
    }

    ~CircleQueue()
    {
       sem_destroy(&sem_blank);
       sem_destroy(&sem_data);
    }
    void P(sem_t& sem)
    {
      sem_wait(&sem);
    }
    void V(sem_t& sem)
    {
      sem_post(&sem);
    }
    void put(const int& in)
    {
        P(sem_blank);
        v[p_index++]=in;
        p_index%=max_cap;
        V(sem_data);
    }
    void get(int& out)
    { 
         P(sem_data);
         out=v[c_index++];
         c_index%=max_cap;
         V(sem_blank);
      
    }
    
};

main.cc

#include"CircleQueue.hpp"


void* consumer(void* cq)
{
    
    CircleQueue* Cq=(CircleQueue*)cq; 
    while(1)
    {
    int out;
    Cq->get(out);
    cout<<"consumer: "<<out<<endl;
    sleep(1);
    }
}

void* productor(void* cq)
{
  //保证让消费者先进去,就挂起了
  sleep(1);
  CircleQueue* Cq=(CircleQueue*) cq;
  while(1)
  {
    
    int in=rand()%10+1;
    Cq->put(in);
    cout<<"productor: "<<in<<endl;
  

  }

}
int main()
{
  CircleQueue *Cq=new CircleQueue;
  pthread_t c,p;
  pthread_create(&c,NULL,consumer,(void*)Cq);
  pthread_create(&p,NULL,productor,(void*)Cq);

  pthread_join(c,NULL);
  pthread_join(p,NULL);
  delete Cq;
}

2.2 实验现象

消费者慢:瞬间生产满,然后消费一个,生产一个
在这里插入图片描述
生产者慢:生产一个,消费一个
在这里插入图片描述
在这里插入图片描述

2.3 和阻塞队列生产者消费者模型的对比

在这里插入图片描述

2.4 实验补充

  • 这两种实验中,为了让生产者先运行,在最开始总是让生产者sleep,先让消费者进入临界区,队列没有任务,然后阻塞。

  • 和上一篇博客中的阻塞队列一样,也可以自定义一个任务类,生产者发布任务,消费者完成任务。

  • 实现多生产者,多消费者,也是创建两把锁,一个用于生产者,一个用于消费者,分别让他们组内竞争,他们的互斥关系,在信号量中已经完成。

  • 注:当只有一个信号量时,叫做二元信号量,不划分临界区,那就相当于锁。

以上是关于Linux多线程_(Posix信号量实现环形队列生产者消费者模型)的主要内容,如果未能解决你的问题,请参考以下文章

linux:线程 POSIX信号量&&线程池

Linux操作系统多线程

Linux信号量

Linux信号量

Linux信号量

基于信号量与环形队列实现读写异步缓存队列