Task.h
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
class Task
{
public:
Task()
{
}
Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitCode(0)
{
}
void operator()()
{
switch (_op)
{
case '+':
_result = _x + _y;
break;
case '-':
_result = _x - _y;
break;
case '*':
_result = _x * _y;
break;
case '/':
{
if (_y == 0)
_exitCode = -1;
else
_result = _x / _y;
}
break;
case '%':
{
if (_y == 0)
_exitCode = -2;
else
_result = _x % _y;
}
break;
default:
break;
}
usleep(100000);//模拟处理一个任务所需要的时间
}
std::string formatArg()
{
return std::to_string(_x) + _op + std::to_string(_y) + "= ?";
}
std::string formatRes()
{
return std::to_string(_result) + "(" + std::to_string(_exitCode) + ")";
}
~Task()
{
}
private:
int _x;
int _y;
char _op;
int _result;
int _exitCode;
};
RingQueue.h
//从环形队列放数据和从环形队列开始拿数据
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
static const int N = 5;
template <class T>
class RingQueue
{
private:
void P(sem_t& s)
{
sem_wait(&s);//等待信号量,会将信号量的值减1
}
void V(sem_t& s)
{
sem_post(&s);//发布信号量,表示资源使用完毕,可以规还资源了,信号量的值加1
}
void Lock(pthread_mutex_t& m)
{
pthread_mutex_lock(&m);
}
void Unlock(pthread_mutex_t& m)
{
pthread_mutex_unlock(&m);
}
public:
RingQueue(int num = N) : _ring(num), _cap(num)
{
sem_init(&_data_sem, 0, 0);//对信号进行初始化,第二个参数零表示线程间通信,非零表示进程间通信,第三个参数是信号量初始值。
sem_init(&_space_sem, 0, num);
_c_step = _p_step = 0;//生产者和消费者初始化的时候下标都是0
pthread_mutex_init(&_c_mutex, nullptr);
pthread_mutex_init(&_p_mutex, nullptr);
}
// 生产
void push(const T& in)
{
// 1. 信号量的好处是可以不用在临界区内部做判断,就可以知道临界资源的使用情况
// 2. 什么时候用锁,什么时候用sem?你对应的临界资源,是否被整体使用!
P(_space_sem); //先申请信号量,看是否有空间资源,再决定进行生产。 P();在生产之前先要进行p操作,现在使用信号量不需要判断是否还有临界资源,因为信号量是一把计数器,本质工作对资源的就绪情况从临界区内转移到了临界区外面。在之前互斥锁那边判断资源是否就绪首先要检查资源,所以得现持有锁,然后再访问资源。而信号量本身就是描述临界资源数量的,所以信号量就省去了加锁进入临界资源判断临界资源数量的工作了。
// 走到这边就一定有对应的空间资源给我!不用做判断环形队列是否有资源(空间)来存放我的数据,曾经我们是申请互斥锁,申请完互斥锁之后我们接下来的操作是判断是否有临界资源,为了知道自己的操作是否满足条件所以要对临界资源进行判断。
// 现在使用的是信号量,我们还用判断临界资源吗?不用了,因为信号量是一把计数器,本质工作就是把对资源的就绪情况由在临界区内转移到临界区外,也就是说我们对临界资源的使用以前在互斥锁那里判断资源是否就绪得先持有锁,然后在做判断。 而信号量不一样,信号量本身就是描述临界资源数量的,所以本质上我们不用直接进入临界区之后在判断临界资源是否满足情况。只有我们p成功了,临界资源就一定就绪,一定程度上简化了编程模型。p失败了线程是会阻塞的。
// 现在的问题是分配到的是哪一个资源呢?这个任务是由程序员来决定的。
Lock(_p_mutex); //? 1
_ring[_p_step++] = in;//生产数据放入环形队列
_p_step %= _cap;//更新下标维护环状队列的环状特点
Unlock(_p_mutex);
V(_data_sem);//对数据进行v操作,也就是数据量加加
}
// 消费
void pop(T* out)
{
P(_data_sem);
Lock(_c_mutex); //先申请信号量再去申请锁是推荐方法,这样就可以当某个线程持有锁的时候,让其它线程先去将资源分配好
*out = _ring[_c_step++];
_c_step %= _cap;
Unlock(_c_mutex);
V(_space_sem);//数据信号量减减就意味着空间资源多了一个
}
~RingQueue()
{
sem_destroy(&_data_sem);//释放信号量
sem_destroy(&_space_sem);
pthread_mutex_destroy(&_c_mutex);
pthread_mutex_destroy(&_p_mutex);
}
private:
std::vector<T> _ring;
int _cap; // 环形队列容器大小
sem_t _data_sem; // 只有消费者关心
sem_t _space_sem; // 只有生产者关心
int _c_step; // 消费位置
int _p_step; // 生产位置,需要两个位置下标是因为生产者和消费者访问的位置是不一样的。也就是p成功之后,决定将哪个资源分配给这个线程。
pthread_mutex_t _c_mutex;//这两把锁是由于多生产和多消费而产生的需求,如果是单生产和单消费是不需要这两把锁的。
pthread_mutex_t _p_mutex;
};
Main.cpp
//从环形队列放数据和从环形队列开始拿数据
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
static const int N = 5;
template <class T>
class RingQueue
{
private:
void P(sem_t& s)
{
sem_wait(&s);//等待信号量,会将信号量的值减1
}
void V(sem_t& s)
{
sem_post(&s);//发布信号量,表示资源使用完毕,可以规还资源了,信号量的值加1
}
void Lock(pthread_mutex_t& m)
{
pthread_mutex_lock(&m);
}
void Unlock(pthread_mutex_t& m)
{
pthread_mutex_unlock(&m);
}
public:
RingQueue(int num = N) : _ring(num), _cap(num)
{
sem_init(&_data_sem, 0, 0);//对信号进行初始化,第二个参数零表示线程间通信,非零表示进程间通信,第三个参数是信号量初始值。
sem_init(&_space_sem, 0, num);
_c_step = _p_step = 0;//生产者和消费者初始化的时候下标都是0
pthread_mutex_init(&_c_mutex, nullptr);
pthread_mutex_init(&_p_mutex, nullptr);
}
// 生产
void push(const T& in)
{
// 1. 信号量的好处是可以不用在临界区内部做判断,就可以知道临界资源的使用情况
// 2. 什么时候用锁,什么时候用sem?你对应的临界资源,是否被整体使用!
P(_space_sem); //先申请信号量,看是否有空间资源,再决定进行生产。 P();在生产之前先要进行p操作,现在使用信号量不需要判断是否还有临界资源,因为信号量是一把计数器,本质工作对资源的就绪情况从临界区内转移到了临界区外面。在之前互斥锁那边判断资源是否就绪首先要检查资源,所以得现持有锁,然后再访问资源。而信号量本身就是描述临界资源数量的,所以信号量就省去了加锁进入临界资源判断临界资源数量的工作了。
// 走到这边就一定有对应的空间资源给我!不用做判断环形队列是否有资源(空间)来存放我的数据,曾经我们是申请互斥锁,申请完互斥锁之后我们接下来的操作是判断是否有临界资源,为了知道自己的操作是否满足条件所以要对临界资源进行判断。
// 现在使用的是信号量,我们还用判断临界资源吗?不用了,因为信号量是一把计数器,本质工作就是把对资源的就绪情况由在临界区内转移到临界区外,也就是说我们对临界资源的使用以前在互斥锁那里判断资源是否就绪得先持有锁,然后在做判断。 而信号量不一样,信号量本身就是描述临界资源数量的,所以本质上我们不用直接进入临界区之后在判断临界资源是否满足情况。只有我们p成功了,临界资源就一定就绪,一定程度上简化了编程模型。p失败了线程是会阻塞的。
// 现在的问题是分配到的是哪一个资源呢?这个任务是由程序员来决定的。
Lock(_p_mutex); //? 1
_ring[_p_step++] = in;//生产数据放入环形队列
_p_step %= _cap;//更新下标维护环状队列的环状特点
Unlock(_p_mutex);
V(_data_sem);//对数据进行v操作,也就是数据量加加
}
// 消费
void pop(T* out)
{
P(_data_sem);
Lock(_c_mutex); //先申请信号量再去申请锁是推荐方法,这样就可以当某个线程持有锁的时候,让其它线程先去将资源分配好
*out = _ring[_c_step++];
_c_step %= _cap;
Unlock(_c_mutex);
V(_space_sem);//数据信号量减减就意味着空间资源多了一个
}
~RingQueue()
{
sem_destroy(&_data_sem);//释放信号量
sem_destroy(&_space_sem);
pthread_mutex_destroy(&_c_mutex);
pthread_mutex_destroy(&_p_mutex);
}
private:
std::vector<T> _ring;
int _cap; // 环形队列容器大小
sem_t _data_sem; // 只有消费者关心
sem_t _space_sem; // 只有生产者关心
int _c_step; // 消费位置
int _p_step; // 生产位置,需要两个位置下标是因为生产者和消费者访问的位置是不一样的。也就是p成功之后,决定将哪个资源分配给这个线程。
pthread_mutex_t _c_mutex;//这两把锁是由于多生产和多消费而产生的需求,如果是单生产和单消费是不需要这两把锁的。
pthread_mutex_t _p_mutex;
};