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

目录

  • 1. 信号量
    • 1.1 是什么
    • 1.2 为什么
    • 1.3 怎么用
      • 1.3.1 初始化信号量
      • 1.3.2 销毁信号量
      • 1.3.3 等待信号量
      • 1.3.4 发布信号量
  • 2. 基于环形队列实现生产者消费者模型
    • 2.1 代码实现
    • 2.2 实验现象
    • 2.3 和阻塞队列生产者消费者模型的对比
    • 2.4 实验补充

1. 信号量

1.1 是什么

信号量也叫做信号灯,本质是一个计数器。
Linux多线程_(Posix信号量实现环形队列生产者消费者模型)_第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 初始化信号量

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

1.3.2 销毁信号量

在这里插入图片描述

1.3.3 等待信号量

在这里插入图片描述

1.3.4 发布信号量

在这里插入图片描述

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

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

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

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

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

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

Linux多线程_(Posix信号量实现环形队列生产者消费者模型)_第3张图片
通俗一点说

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

那么就引入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
#include
#include
#include
#include
#include
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 实验现象

消费者慢:瞬间生产满,然后消费一个,生产一个
Linux多线程_(Posix信号量实现环形队列生产者消费者模型)_第4张图片
生产者慢:生产一个,消费一个
Linux多线程_(Posix信号量实现环形队列生产者消费者模型)_第5张图片
Linux多线程_(Posix信号量实现环形队列生产者消费者模型)_第6张图片

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

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

2.4 实验补充

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

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

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

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

你可能感兴趣的:(Linux操作系统,操作系统,并发编程)