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个问题。
-
假如生产苹果,生产者生产5个,消费者消费了2个,生产者继续生产2个,他不能继续生产第三个,因为人家还没消费,你在生产就覆盖掉了。
-
假如消费苹果,生产者生产5个,消费者消费了5个,生产者还没生产,你不能继续消费,因为人家还没生产。
-
当两者处于同一位置,为空条件,消费者不能运行。
-
当两者处于同一位置,为满条件,生产者不能运行。
信号量将这些问题全部解决。
- 生产5个,格子资源为0,数据资源为5,消费两个,格子资源为2,数据资源为3,生产两个,格子资源为0,数据资源为5。由于没有格子资源,所以不会再生产。
- 生产5个,格子资源为0,数据资源为5,消费5个,格子资源为5,数据资源为0, 由于没有数据资源,所以不会在消费。
- 处于同一位置,为空,消费者不会运行,因为数据资源为0。
- 处于同一位置,为满,生产者不会运行,因为格子资源为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信号量实现环形队列生产者消费者模型)的主要内容,如果未能解决你的问题,请参考以下文章