信号量概念
这篇文章是以前写的,里面讲了 System V的信号量的概念,POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
POSIX信号量的接口
初始化信号量
参数:
pshared:0表示线程间共享,非0表示进程间共享
value:信号量初始值,资源的初始值
销毁信号量
等待信号量,P操作,表示要使用资源,将信号量值加1
发布信号量,V操作,表示资源使用完毕,可以归还资源了,将信号量值加1
基于环形队列的生产消费者模型
这篇文章里的生产者消费者模型,是基于一个queue展开了,也就是把queue当成一个整体来看,当成一个资源,我们对他进行加锁保护(锁就相当于二元信号量)。下面我们把这个生产者消费者模型改一下,是基于环形队列的,不把环形队列看成一个整体,把环形队列里的每个块看成很多个资源,这时候线程并发访问时会产生问题,就需要用信号量来解决
环形队列采用数组模拟,用模运算来模拟环状特性,生产者Prpducter向队列里生产任务,消费者Consumer向队列里拿数据
怎么判空和满?
当P,V指向同一个位置的时候,就是空或者满
怎么保证它们不访问同一个位置? 用信号量来管理
生产者关注的是空间资源,消费者关注的是数据资源,这两种资源可以分别用两种信号量来管理 ,假如开始的时候,空间资源是队列的大小,数据资源为0,此时生产者可以申请空间资源信号量成功,他就可以生产,但是消费者申请数据资源信号量时就失败等待,用两个信号量来管理者两个资源就可以很好的让生产者消费者同时访问同一个队列,但是不会访问到同一个位置,就可以很好的解决上面的问题
实现代码
大家可以把他们复制到VS Code下来看
cpp
#include<iostream>
#include<ctime>
#include<unistd.h>
#include"RingQueue.hpp"
#include"task.hpp"
using namespace std;
struct ThreadData
{
RingQueue<Task>* rq;
string threadname;
};
void* Consumer(void* args)
{
ThreadData* data=static_cast<ThreadData*>(args);
RingQueue<Task>* rq=data->rq;
string name=data->threadname;
while(true)
{
//消费任务
Task t;
rq->Pop(&t);
//处理任务
t.run();
printf("消费者得到一个任务:%s,who:%s,结果:%s\n",t.GetTask().c_str(),name.c_str(),t.GetResult().c_str());
//用printf输出比较好,不会错乱,cout打印有时会错乱
// cout<<"消费者得到一个任务:"<<t.GetTask()<<",who: "<<name<<",结果为:"<<t.GetResult()<<endl;
// sleep(1);
}
return nullptr;
}
void* Producter(void* args)
{
ThreadData* data=static_cast<ThreadData*>(args);
RingQueue<Task>* rq=data->rq;
string name=data->threadname;
while(true)
{
//获取数据
int data1=rand()%10+1;
int data2=rand()%5;
char op=opers[rand()%opers.size()];
Task t(data1,data2,op);
//生产任务
rq->Push(t);
printf("生产者生产一个任务:%s,who:%s\n",t.GetTask().c_str(),name.c_str());
//用printf输出比较好,不会错乱,cout打印有时会错乱
// cout<<"生产者生产一个任务:"<<t.GetTask()<<",who: "<<name<<endl;
sleep(1);
}
return nullptr;
}
int main()
{
srand(time(nullptr));
RingQueue<Task>* rq=new RingQueue<Task>(10);
pthread_t c[5], p[3];
for (int i = 0; i < 3; i++)
{
ThreadData *td = new ThreadData();
td->rq = rq;
td->threadname = "Productor-" + std::to_string(i);
pthread_create(p + i, nullptr, Producter, td);
}
for (int i = 0; i < 5; i++)
{
ThreadData *td = new ThreadData();
td->rq = rq;
td->threadname = "Consumer-" + std::to_string(i);
pthread_create(c + i, nullptr, Consumer, td);
}
for (int i = 0; i < 3; i++)
{
pthread_join(p[i], nullptr);
}
for (int i = 0; i < 5; i++)
{
pthread_join(c[i], nullptr);
}
return 0;
}
RingQueue.hpp
cpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include <semaphore.h>
using namespace std;
template<class T>
class RingQueue
{
public:
void P(sem_t& sem)
{
sem_wait(&sem);
}
void V(sem_t& sem)
{
sem_post(&sem);
}
void Lock(pthread_mutex_t& mutex)
{
pthread_mutex_lock(&mutex);
}
void Unlock(pthread_mutex_t& mutex)
{
pthread_mutex_unlock(&mutex);
}
public:
RingQueue(int maxcap=5):maxcap_(maxcap),ringqueue_(maxcap),c_index_(0),p_index_(0)
{
sem_init(&cdata_sem_,0,0);
sem_init(&pspace_sem_,0,maxcap);
pthread_mutex_init(&c_mutex_,nullptr);
pthread_mutex_init(&p_mutex_,nullptr);
}
void Push(const T& in)//生产
{
P(pspace_sem_);//生产者关注空间资源,申请空间资源,空间资源--
Lock(p_mutex_);//下标也是共享资源,需要加锁保护
ringqueue_[p_index_]=in;
p_index_++;
p_index_%=maxcap_;//维持环形特性
Unlock(p_mutex_);
V(cdata_sem_);//数据资源增多了,数据资源++
}
void Pop(T* out)//消费
{
P(cdata_sem_);//消费者关注数据资源,申请数据资源,数据资源--
Lock(c_mutex_);//下标也是共享资源,需要加锁保护
*out=ringqueue_[c_index_];
c_index_++;
c_index_%=maxcap_;//维持环形特性
Unlock(c_mutex_);
V(pspace_sem_);//消费了数据,空间就增多了
}
~RingQueue()
{
sem_destroy(&cdata_sem_);
sem_destroy(&pspace_sem_);
pthread_mutex_destroy(&c_mutex_);
pthread_mutex_destroy(&p_mutex_);
}
private:
vector<T> ringqueue_;//用数组模拟环形队列
int maxcap_;//环形队列的最大容量
int c_index_;//消费者的下标
int p_index_;//生产者的下标
sem_t cdata_sem_;//消费者关注的数据资源
sem_t pspace_sem_;//生产者关注的空间资源
pthread_mutex_t c_mutex_;
pthread_mutex_t p_mutex_;
};
Task.hpp
cpp
#pragma once
#include <iostream>
#include <string>
using namespace std;
string opers="+-*/%";
enum
{
Divzero = 1,
Modzero,
Unknown
};
class Task
{
public:
Task()
{}
Task(int data1, int data2, char op) : _data1(data1), _data2(data2), _op(op), _result(0), _exitcode(0)
{}
void run()
{
switch (_op)
{
case '+':
{
_result = _data1+_data2;
break;
}
case '-':
{
_result = _data1-_data2;
break;
}
case '*':
{
_result = _data1*_data2;
break;
}
case '/':
{
if (_data2 == 0) _exitcode = Divzero;
else _result = _data1/_data2;
break;
}
case '%':
{
if (_data2 == 0) _exitcode = Modzero;
else _result = _data1%_data2;
break;
}
default:
{
_exitcode=Unknown;
break;
}
}
}
void operator()()
{
run();
}
string GetResult()
{
string r=to_string(_data1);
r+=_op;
r+=to_string(_data2);
r+='=';
r+=to_string(_result);
r+='[';
r+=to_string(_exitcode);
r+=']';
return r;
}
string GetTask()
{
string r=to_string(_data1);
r+=_op;
r+=to_string(_data2);
r+="=?";
return r;
}
private:
int _data1;
int _data2;
char _op;
int _result;
int _exitcode;
};