Linux多线程(信号量与环形队列)
@[toc]
一、信号量
1.信号量概念
在学习SystemV的时候,就提到过信号量这一概念。
信号量的本质就是一个计数器,用来描述临界资源数目的大小(即有多少资源可以分配给线程)。使用信号量的本质其实是预订资源(预订后才能使用)。可以通过信号量让不同的线程访问临界资源的不同区域,从而实现并发。
2.信号量系统接口
如果我们使用普通的计数器count来记录临界资源,显然count也是临界资源,因此是不安全的。所以OS提供了一系列与信号量有关的接口:
(1)sem_init
用于对信号量进行初始化,第一个参数为指向一个信号量的指针,第二个函数代表是否进程之间共享,这里设为0即可。第三个参数表示信号量的数量,需要我们自己设置。
(2)sem_destroy
即销毁信号量,参数为要销毁的信号量。
(3)sem_wait
sem_wait操作执行的是信号量–的操作:
用伪代码可以表示为:
start:
lock();
if(count<=0)
{
wait;//挂起
}
else
{
count--;
}
(4)sem_post
sem_post执行的是信号量++的操作。
它的伪代码如下:
lock();
count++;
unlock();
二、环形队列
环形队列最早在数据结构那里学习过,我们使用数组来模拟环形队列,当遍历到数组结尾的时候,回到数组首元素位置。
当拿和放指向同一个位置的时候,环形队列既可能是空,也可能是满。因此我们需要作一个判断。
1.可以定义一个镂空的位置,当当前位置+1!=拿的时候可以放,否则队列已满,不再放。
2.可以定义一个计数器,每放一次就++,每拿一次就–,根据计数器的值来判断队列是否已满。
三、利用信号量和环形队列来实现生产者消费者模型
使用信号量和环形队列的好处在于,可以让消费者和生产者可以不同时访问同一块临界资源,因此不需要加锁。
1.实现思路
(1)同步与互斥
当生产者和消费者指向同一个位置的时候,队列可能为空也可能为满,因此当队列不为空,不为满的时候生产者和消费者一定指向的不是同一个位置。因此生产和消费可以并发执行,不用加锁,符合互斥特性。
当队列为空时,生产者执行,队列为满时,消费者执行,这也体现了局部上的同步特性。
(2)规则
生产者关心的是环形队列中空的位置,消费者关心的是环形队列中的数据。
1.生产者不能对消费者扣圈。
2.消费者不能超过生产者。
3.当指向同一个位置的时候,要根据空或者满的状态来判断让谁先执行。
(3)信号量思路
当生产者生产数据后,blank信号量–,data信号量++。当有data后,消费者开始消耗数据,即data信号量–,blank信号量++。从而实现环状结构。
2.具体实现
(1)ring_queue.hpp
#include<iostream>
#include<vector>
#include<pthread.h>
#include<semaphore.h>
#include<unistd.h>
#include<stdlib.h>
using namespace std;
namespace ns_ring_queue
{
template<class T>
class RingQueue
{
private:
vector<T> ring_queue_;
int cap_;
sem_t blank_sem_;//空格资源信号量
sem_t data_sem_;//数据资源信号量
int c_step_;//消费者位置
int p_step_;//生产者位置
pthread_mutex_t con;
pthread_mutex_t pro;
public:
RingQueue(int cap=10):cap_(cap),ring_queue_(cap)
{
sem_init(&blank_sem_,0,cap);
sem_init(&data_sem_,0,0);
c_step_=p_step_=0;
}
void Push(const T& in)
{
sem_wait(&blank_sem_);
pthread_mutex_lock(&pro);
ring_queue_[p_step_]=in;
p_step_++;
p_step_%=cap_;
pthread_mutex_unlock(&pro);
sem_post(&data_sem_);
}
void Pop(T* out)
{
sem_wait(&data_sem_);
pthread_mutex_lock(&con);
*out=ring_queue_[c_step_];
c_step_++;
c_step_%=cap_;
pthread_mutex_unlock(&con);
sem_post(&blank_sem_);
}
~RingQueue()
{
sem_destroy(&blank_sem_);
sem_destroy(&data_sem_);
}
};
}
(2)ring_cp.cc
#include"ring_queue.hpp"
using namespace std;
using namespace ns_ring_queue;
void* consumer(void* args)
{
RingQueue<int>* rq=(RingQueue<int>*)args;
while(true)
{
int data=0;
rq->Pop(&data);
cout<<"正在被线程"<<pthread_self()<<"消费的数据是:"<<data<<endl;
sleep(1);
}
}
void* productor(void* args)
{
RingQueue<int>* rq=(RingQueue<int>*)args;
while(true)
{
int data=rand()%20+1;
cout<<"正在被线程"<<pthread_self()<<"生产"<<"生产的数据是:"<<data<<endl;
rq->Push(data);
}
}
int main()
{
RingQueue<int>* rq=new RingQueue<int>();
pthread_t c1,c2,c3,c4,p1,p2,p3;
pthread_create(&c1,nullptr,consumer,(void*)rq);
pthread_create(&c2,nullptr,consumer,(void*)rq);
pthread_create(&c3,nullptr,consumer,(void*)rq);
pthread_create(&c4,nullptr,consumer,(void*)rq);
pthread_create(&p1,nullptr,productor,(void*)rq);
pthread_create(&p2,nullptr,productor,(void*)rq);
pthread_create(&p3,nullptr,productor,(void*)rq);
pthread_join(c1,nullptr);
pthread_join(c2,nullptr);
pthread_join(c3,nullptr);
pthread_join(c4,nullptr);
pthread_join(p1,nullptr);
pthread_join(p2,nullptr);
pthread_join(p3,nullptr);
}
注意,由于生产者消费者不会访问同一块资源,因此消费者和生产者之间不用加锁。而生产者和生产者之间,消费者和消费者之间要访问同一块资源,因此需要进行加锁。
- 点赞
- 收藏
- 关注作者
评论(0)