多线程——阻塞队列、信号量、环形队列

文章目录

  • 1.基于阻塞队列实现生产者消费者模型
    • 1.1阻塞队列应用场景
    • 1.2消费者模型代码实现任务
    • 1.3代码内容知识点预知
      • 1.3.1判断时用while而不是if
      • 1.3.2为什么等待函数中需要传入锁
    • 1.4实现代码和效果展示
      • 1.4.1代码
      • 1.4.2实现效果
  • 2.POSIX信号量
    • 2.1是什么
    • 2.2为什么PV操作设计成计数器模式
    • 2.3信号量的使用接口
  • 3.基于环形队列的生产者消费者模型
    • 3.1理论预知
    • 3.2实验代码
    • 3.3实验现象

1.基于阻塞队列实现生产者消费者模型

1.1阻塞队列应用场景

多线程——阻塞队列、信号量、环形队列_第1张图片
多线程——阻塞队列、信号量、环形队列_第2张图片

1.2消费者模型代码实现任务

任务:
实现多个生产者往阻塞队列里面写入数据、一个消费者从阻塞队列中读取数据(阻塞队列也可以叫做用户级别的消息队列)

模型:
1.角色:生产者、消费者

2.关系
生产者vs生产者:互斥关系,一次只有一个线程能够往阻塞队列里面写数据
生产者vs消费者:之间的关系是同步的、生产者将数据填满阻塞队列才会叫消费者去读取,消费者读完数据,阻塞队列为空才会叫生产者去生产

3.场所:
交易场所是阻塞队列

1.3代码内容知识点预知

1.3.1判断时用while而不是if

多线程——阻塞队列、信号量、环形队列_第3张图片

1.3.2为什么等待函数中需要传入锁

多线程——阻塞队列、信号量、环形队列_第4张图片

1.4实现代码和效果展示

1.4.1代码

#pragma once
#include 
#include 
#include 
using namespace std;
#include 

class Test
{
  public:
  int x;
  int y;

};

template<class T>
class BlockQueue
{
  private:
    queue <T> _q;//阻塞队列
    size_t _cap;//容量
    pthread_mutex_t lock;//锁
    pthread_cond_t p_cond;//生产者条件变量
    pthread_cond_t c_cond;//消费者条件变量

  public:

    BlockQueue(size_t cap)
      :_cap(cap)
    {
      pthread_mutex_init(&lock,nullptr);
      pthread_cond_init(&p_cond,nullptr);
      pthread_cond_init(&c_cond,nullptr);
    }

    ~BlockQueue()
    {
      pthread_mutex_destroy(&lock);
      pthread_cond_destroy(&p_cond);
      pthread_cond_destroy(&c_cond);
    }

    void LockQueue()
    {
      pthread_mutex_lock(&lock);
    }

    void unLockQueue()
    {
      pthread_mutex_unlock(&lock);
    }

    bool Full()
    {
      return _q.size()>=_cap;//满了
    }

    bool Empty()
    {
      return _q.empty();
    }
    void WakeUpConsumer()
    {
      cout<<"wake up consemer.....\n";
      pthread_cond_signal(&c_cond);
    }
    void WakeUpProducter()
    {
      cout<<"wake up producter......\n";
      pthread_cond_signal(&p_cond);
    }
    void ConsumerWait()
    {
      cout<<"consumer wait.....\n";
      pthread_cond_wait(&c_cond,&lock);
    }
    void ProducterWait()
    {
      cout<<"producter wait.......\n";
      pthread_cond_wait(&p_cond,&lock);
    }


    void Put(const T &data)//放数据
    {
      LockQueue();//加锁
      
      while(Full())//满起自己,通知消费者
      {
        WakeUpConsumer();//队列满了,唤醒消费者
        ProducterWait();//自己挂起
      }
        _q.push(data);

      unLockQueue();//解锁
    }

    void Take(T &data)//拿数据,输出型参数
    {
      LockQueue();

      while(Empty())//使用while防止异常、没有挂起
      {
        WakeUpProducter();//唤醒生产者
        ConsumerWait();//自己挂起
      }

      data=_q.front();
      _q.pop();

      unLockQueue();
    }
};

```cpp
#include"bq.hpp"

pthread_mutex_t lock;
void *PutData(void *arg)
{
  BlockQueue<Test> *q=(BlockQueue<Test>*)arg;
  
  while(1)
  {
    pthread_mutex_lock(&lock);//加锁

    cout<<"thread"<<pthread_self()<<"put data"<<endl;//当前是那个生产者
    Test data;
    
    data.x=rand()%5;
    data.y=rand()%10;

    q->Put(data);//将随机生成的数据插入阻塞队列之中
    
    cout<<"put data:"<<data.x<<"+"<<data.y<<"=?"<<endl;

    pthread_mutex_unlock(&lock);//解锁

    usleep(1);//线程切换是在内核态回用户态时,增加系统调用,加多判断
  }
}

void *GetData(void *arg)
{
  BlockQueue<Test> *q=(BlockQueue<Test>*)arg;

  while(1)
  {
    sleep(1);
    Test data;
    q->Take(data);

    cout<<"get data:"<<data.x<<"+"<<data.y<<"="<<data.x+data.y<<endl;//打印拿到的数据
  }

}

int main()
{
  pthread_t tid1,tid2,tid3,tid4;
  BlockQueue<Test> *q =new BlockQueue<Test>(5);//也可以定义成全局变量
  pthread_mutex_init(&lock,nullptr);//多个生产者,实现互斥




  pthread_create(&tid1,nullptr,GetData,(void*)q);//消费者拿数据
  pthread_create(&tid2,nullptr,PutData,(void*)q);//生产者生产数据
  pthread_create(&tid3,nullptr,PutData,(void*)q);//生产者生产数据
  pthread_create(&tid4,nullptr,PutData,(void*)q);//生产者生产数据

  //等待不关心返回值
  pthread_join(tid1,nullptr);
  pthread_join(tid2,nullptr);
  pthread_join(tid3,nullptr);
  pthread_join(tid4,nullptr);
  

  pthread_mutex_destroy(&lock);
  delete q;

  return 0;
}

1.4.2实现效果

多线程——阻塞队列、信号量、环形队列_第5张图片

2.POSIX信号量

POSIX信号量和System V信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步

2.1是什么

信号量(信号灯)本质是一种计数器,描述临界资源有效个数的计数器
多线程——阻塞队列、信号量、环形队列_第6张图片
多线程——阻塞队列、信号量、环形队列_第7张图片

举个例子:

场景:电影院(临界区)
进入对象:人(线程)
竞争资源:座位(临界资源)
座位计数器:票数(计数器)

假设电影是无限开放的,并且异常火爆,只有买到票的人才可以进入观看,这时为了维护秩序,就需要用票数来控制进入影院的人数,一张票对应一个座椅,有人进入则票数减一, 有人出来则票数加一

2.2为什么PV操作设计成计数器模式

将临界资源看成多份之后,可以使得多个线程进入临界区访问临界资源,并且不发生冲突,因此可以提高效率

而信号灯的作用就是维护线程之间的互斥关系,确保进入的线程不会超过临界资源的个数

2.3信号量的使用接口

多线程——阻塞队列、信号量、环形队列_第8张图片
销毁信号量:
多线程——阻塞队列、信号量、环形队列_第9张图片
等待信号量:
等待信号量,信号量的值-1
多线程——阻塞队列、信号量、环形队列_第10张图片
发布信号量:
表示资源使用完毕,归还资源,信号量的值+1
多线程——阻塞队列、信号量、环形队列_第11张图片

3.基于环形队列的生产者消费者模型

3.1理论预知

多线程——阻塞队列、信号量、环形队列_第12张图片
多线程——阻塞队列、信号量、环形队列_第13张图片

3.2实验代码

#pragma once
#include 
#include 
#include 
#include 
#include 
#define  NUM 5

class RingQueue
{
  private:
    std::vector<int> _q;
    int _cap;
    //信号量
    sem_t sem_blank;
    sem_t sem_data;
  //资源下标
    int pro_sub;
    int con_sub;

  private:
    void P(sem_t &sem)
    {
      sem_wait(&sem);
    }

    void V(sem_t &sem)
    {
      sem_post(&sem);
    }


  public:
  RingQueue(int cap=NUM)
    :_cap(cap)
     ,_q(cap)
  {
    sem_init(&sem_blank,0,_cap);//格子个数一开始为容量大小
    sem_init(&sem_data,0,0);//数据一开始为0
    pro_sub=0;
    con_sub=0;
  }
  
  void Put(const int &data)
  {
    P(sem_blank);//申请格子资源,判断是否还有容量,格子--
    _q[pro_sub]=data;
    pro_sub++;
    pro_sub%=_cap;//越界回环
    V(sem_data);//数据++
  }

  void Get(int &data)
  {
    P(sem_data);//申请数据资源,判断是否还有数据,数据--
    data=_q[con_sub];
    con_sub++;
    con_sub%=_cap;
    V(sem_blank);//消耗数据,格子++
  }

  ~RingQueue()
  {
    sem_destroy(&sem_blank);
    sem_destroy(&sem_data);
    pro_sub=con_sub=0;
  }

};
#include "RingQueue.hpp"

pthread_mutex_t pro_lock;//生产者组内竞争锁
pthread_mutex_t con_lock;//消费者组内竞争锁

int count=0;//生产者数据

void Lock(pthread_mutex_t &lock)
{
  pthread_mutex_lock(&lock);
}

void ULock(pthread_mutex_t &lock)
{
  pthread_mutex_unlock(&lock);
}

  void *Get(void *arg)
{
  RingQueue *q=(RingQueue*)arg;
  while(1)
  {
    usleep(1);
    int data=0;
    Lock(con_lock);//组内竞争
    q->Get(data);//获取数据
    ULock(con_lock);
    std::cout<<pthread_self()<<":consumer get data...:"<<data<<std::endl;
  }
}
void *Put(void *arg)
{
  RingQueue *q=(RingQueue*)arg;
  while(1)
  {
    sleep(1);//增加系统调用
    Lock(pro_lock);//组内竞争
    q->Put((++count)%10);
    ULock(pro_lock);
    std::cout<<pthread_self()<<":productor put data...."<<std::endl;
  }

}

int main()
{
//创建交易场所
  RingQueue *q=new RingQueue(5);

  //初始化锁
  pthread_mutex_init(&con_lock,nullptr);
  pthread_mutex_init(&pro_lock,nullptr);
  
  //创建线程
  pthread_t tid1,tid2,tid3,tid4,tid5,tid6;
  pthread_create(&tid1,nullptr,Put,q);
  pthread_create(&tid2,nullptr,Put,q);
  pthread_create(&tid3,nullptr,Put,q);
  pthread_create(&tid4,nullptr,Get,q);
  pthread_create(&tid5,nullptr,Get,q);
  pthread_create(&tid6,nullptr,Get,q);


//等待线程、避免内存泄漏,不关心返回值
  pthread_join(tid1,nullptr);
  pthread_join(tid2,nullptr);
  pthread_join(tid3,nullptr);
  pthread_join(tid4,nullptr);
  pthread_join(tid5,nullptr);
  pthread_join(tid6,nullptr);

  //销毁工作
  pthread_mutex_destroy(&con_lock);
  pthread_mutex_destroy(&pro_lock);
  delete q;
  
  return 0;
}

3.3实验现象

多线程——阻塞队列、信号量、环形队列_第14张图片

你可能感兴趣的:(Linux,Linux多线程,环形队列,生产消费者,阻塞队列,信号量)