目录
一,信号量的概念
1,概念
在linux下,有一个库叫做#include<semaphore.h>。在这个库里面便有一个信号量的类型:
sem_t 。原型如下:
typedef union { char __size[__SIZEOF_SEM_T]; long int __align; } sem_t;
这个便是信号量在语言上的体现。那信号量是什么呢?其实信号量在本质上就是一个描述资源数量的计数器。通过信号量来申请资源不需要判断资源是否就绪或者存在,并且对于信号量操作是原子的(只有两种状态,要么你成功了,要么失败了),所以对于信号量的操作是线程安全的。
2,例子
对于上面的概念可以用一个买电影票的例子来理解。当你要去看电影时,你是需要买票的,买票就相当于去申请资源,你买到票那你就是申请到了资源你便可以使用这个资源而不用去管这个资源是否还有(因为这是电影院要做的事),如果没买到票那就是申请资源失败了你就不能使用这个资源。所以你买电影票的状态只有两种,同样的你申请资源的状态也只有两种。
二,信号量操作函数
1,sem_init函数
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem: 代表你定义的信号量
pshared: 是一个整型,等于0时代表是线程间的信号量,非零代表是进程间的信号量
value: 信号量的初始化值。
2, sem_wait函数
int sem_wait(sem_t *sem);
这个函数相当于PV操作里的P操作,申请资源,会将信号量sem--。
3, sem_post函数
int sem_post(sem_t *sem);
这个函数相当于PV操作里的V操作,归还资源,会将信号量++。
4,sem_destroy函数
int sem_destroy(sem_t *sem);
销毁掉信号量sem。
三,基于信号量实现循环队列版的生产消费者模型
1,消费场所设计
在这里实现的消费场所还是一个队列。这个队列内的元素有如下几种:
1,两个信号量:分别管理着空间资源和数据资源。
2,两个下标:分别代表着消费者和生产者的步调。
3,两把锁:分别保证在多生产多消费的情况下生产数据和插入数据时只有一个生产者和消费者在操作。
4,队列的最大容量:代表消费空间最多有多大空间。
循环队列的代码如下:
#include<iostream>
#include<vector>
#include<pthread.h>
#include<semaphore.h>
#include<unistd.h>
using std::cin;
using std::cout;
static int defaultnum = 10;//默认空间大小
template<class T>
class Blockqueue
{
public:
//封装P,V操作:P代表申请资源资源--,V代表归还资源资源++。
void P( sem_t & sem)
{
sem_wait(&sem);
}
void V( sem_t &sem)
{
sem_post(&sem);
}
Blockqueue()
:max_cp_(defaultnum),step_c_(0),step_p_(0),queue(defaultnum)
{
//初始化信号量
sem_init(&sem_space, 0, max_cp_);
sem_init(&sem_data, 0, 0);
//初始化锁
pthread_mutex_init(&mutex_p_, nullptr);
pthread_mutex_init(&mutex_c_, nullptr);
}
void push(const T& in)//生产者
{
//先申请空间资源
P(sem_space);
pthread_mutex_lock(&mutex_p_);//加锁
queue[step_p_] = in;//加入数据
step_p_++;
step_p_ %= max_cp_;
pthread_mutex_unlock(&mutex_p_);//解锁
V(sem_data);//数据资源加1
usleep(100);
}
void pop(T* out)
{
//先申请数据资源
P(sem_data);
pthread_mutex_lock(&mutex_c_);//加锁
*out = queue[step_c_];//获取数据
step_c_++;
step_c_ %= max_cp_;
pthread_mutex_unlock(&mutex_c_);//解锁
V(sem_space);//空间资源+1
usleep(100);
}
~Blockqueue()
{
//销毁信号量
sem_destroy(&sem_data);
sem_destroy(&sem_space);
//销毁锁
pthread_mutex_destroy(&mutex_c_);
pthread_mutex_destroy(&mutex_p_);
}
private:
pthread_mutex_t mutex_p_;//生产者的锁
pthread_mutex_t mutex_c_;//消费者的锁
sem_t sem_space; // 空间信号量
sem_t sem_data;//数据信号量
int step_c_; //消费者步调
int step_p_;//生产者步调
int max_cp_;//最大空间
vector<T> queue;//数组模拟队列
};
这里有一个细节:加锁和解锁要在申请完信号量和释放完信号量之后再完成,这样能提高代码效率并且符合加锁的原则(保证临界区的代码尽量少)。
2,任务设计
这次的任务设计仍然是一个计算的任务。
#include<iostream>
#include<string>
using namespace std;
enum
{
Exitcode = 1,
Modexit ,
Unkonw
};
class task//创建一个任务类来执行任务
{
public:
task()
{}
task(int x,int y,char op)
:data1_(x),data2_(y),oper_(op),exit_code_(0),result_(0)
{}
void run()//运行任务:+-*/
{
switch(oper_)
{
case '+':
{
result_ = data1_ + data2_;
}
break;
case '-':
{
result_ = data1_ - data2_;
}
break;
case '*':
{
result_ = data1_ * data2_;
}
break;
case '/':
{
if(data2_ == 0)
{
exit_code_ = Exitcode;
}
else
{
result_ = data1_ / data2_;
}
}
break;
case '%':
{
if(data2_ == 0)
{
exit_code_ = Modexit;
}
else
{
result_ = data1_ % data2_;
}
}
break;
default:
{
exit_code_ = Unkonw;
}
break;
}
}
string Get_result()//将结果显示出来
{
string r;
r+=to_string(data1_);
r += oper_;
r+=to_string(data2_);
r+= '=';
r += to_string(result_);
r += '[';
r += to_string(exit_code_);
r += ']';
return r;
}
string Show_task()//显示任务
{
string r;
r += to_string(data1_);
r += oper_;
r += to_string(data2_);
r += '=';
r+='?';
return r;
}
~task()
{
}
private:
int data1_;//数据1
int data2_;//数据2
char oper_;//运算符
int exit_code_; //退出码
int result_;//运算结果
};
3,Main函数逻辑
Main函数内的逻辑十分的简单,就是创建多个消费者线程和生产者线程分别执行生产任务和消费任务。执行完毕后回收线程。
#include "Task.hpp"
#include "sem_Blockqueue.hpp"
#include <time.h>
string oper = "+-*/";
const int len = oper.size();
void *Productor(void *args)
{
Blockqueue<task> *q = static_cast<Blockqueue<task> *>(args);
while (true)
{
//计算数据
int data1 = rand() % 10 + 1;
int data2 = rand() % 10 + 1;
char op = oper[rand() % len];
//插入任务
task t(data1, data2, op);
q->push(t);
cout << "插入一个任务: " << t.Show_task() << endl;
sleep(1);
}
}
void *Comsumer(void *args)
{
Blockqueue<task> *q = static_cast<Blockqueue<task> *>(args);
while (true)
{
//获得并执行任务
task t;
q->pop(&t);
t.run();
cout << "得到一个任务 " << t.Get_result() << endl;
}
}
int main()
{
Blockqueue<task> *q = new Blockqueue<task>();//创建交易场所
srand(time(nullptr));
//创建线程
pthread_t c[1], p[1];
for (int i = 0; i < 1; i++)
{
pthread_create(p + i, nullptr, Productor, q);
}
for (int i = 0; i < 1; i++)
{
pthread_create(c + i, nullptr, Comsumer, q);
}
//回收线程
for (int i = 0; i < 1; i++)
{
pthread_join(p[i], nullptr);
}
for (int i = 0; i < 1; i++)
{
pthread_join(c[i], nullptr);
}
return 0;
}