目录
[(1) void P(sem_t &sem);](#(1) void P(sem_t &sem);)
[(2) void V(sem_t &sem);](#(2) void V(sem_t &sem);)
[(3) RingQueue()](#(3) RingQueue())
[(4) ~RingQueue()](#(4) ~RingQueue())
[(5) void Push(const T &in);](#(5) void Push(const T &in);)
[(6) void Pop(T *out);](#(6) void Pop(T *out);)
[(1) RingQueue()](#(1) RingQueue())
[(2) ~RingQueue()](#(2) ~RingQueue())
[(3) void Push(const T &in);](#(3) void Push(const T &in);)
[(4) void Pop(T *out);](#(4) void Pop(T *out);)
这篇博客的重点在于代码实现,理论部分请看CSDN
一、单生产单消费
1.环形队列的实现
单生产单消费的情况下,我们只需要维护生产者和消费者之间的互斥和同步关系即可
将环形队列封装成一个类:首先给出整体框架,接着会说明每一个类内函数的实现
cpp
#pragma once
#include <iostream>
#include <vector>
#include <cassert>
#include <semaphore.h>
// 环形队列的默认大小
static const int gcap = 5;
// 设置成模版类型,队列中可以放任意类型的数据
template <class T>
class RingQueue
{
private:
// 封装系统调用sem_wait
void P(sem_t &sem);
// 封装系统调用sem_post
void V(sem_t &sem);
public:
RingQueue()
~RingQueue()
// 生产者向队列里放数据
void Push(const T &in);
// 消费者从队列中取数据
void Pop(T *out);
private:
std::vector<T> _queue; // 数组模拟环形队列
int _cap; // 环形队列的容量
sem_t _spaceSem; // 生产者 -> 空间资源
sem_t _dataSem; // 消费者 -> 数据资源
int _producerStep; // 生产者的位置(数组下标)
int _consumerStep; // 消费者的位置(数组下标)
};
(1) void P(sem_t &sem);
封装系统调用sem_wait,便于在push和pop中使用
cpp
void P(sem_t &sem)
{
int n = sem_wait(&sem);
assert(n == 0);
(void)n;
}
(2) void V(sem_t &sem);
封装系统调用sem_post,便于在push和pop中使用
cpp
void V(sem_t &sem)
{
int n = sem_post(&sem);
assert(n == 0);
(void)n;
}
(3) RingQueue()
cpp
RingQueue(const int &cap = gcap)
: _queue(cap), _cap(cap)
{
// 初始化信号量
int n = sem_init(&_spaceSem, 0, _cap);
assert(n == 0);
n = sem_init(&_dataSem, 0, 0);
assert(n == 0);
// 初始化生产者和消费者的位置
_productorStep = _consumerStep = 0;
}
(4) ~RingQueue()
cpp
~RingQueue()
{
// 销毁信号量
sem_destroy(&_spaceSem);
sem_destroy(&_dataSem);
}
(5) void Push(const T &in);
生产者向环形队列里放数据
cpp
void Push(const T &in)
{
// 生产者要了一个空间,空间信号量--
P(_spaceSem);
// 把数据放进队列
_queue[_producerStep++] = in;
// 维护环状结构
_producerStep %= _cap;
// 队列新增了数据,数据信号量++
V(_dataSem);
}
(6) void Pop(T *out);
消费者从环形队列中取数据
cpp
void Pop(T *out)
{
// 消费者拿了一个数据,数据信号量--
P(_dataSem);
// 把数据拿出队列
*out = _queue[_consumerStep++];
// 维护环状结构
_consumerStep %= _cap;
// 队列多出了空间,空间信号量++
V(_spaceSem);
}
2.上层调用逻辑
cpp
#include "RingQueue.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
// 生产者线程
void *ProducerRoutine(void *rq)
{
// 通过参数rq拿到环形队列
RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);
// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);
// 生产活动
while (true)
{
int data = rand() % 10 + 1; // 模拟构建任务
ringqueue->Push(data); // 把任务放进环形队列
std::cout << "生产完成,生产的数据是:" << data << std::endl;
}
}
// 消费者线程
void *ConsumerRoutine(void *rq)
{
// 通过参数rq拿到环形队列
RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);
// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);
// 消费活动
while (true)
{
int data;
ringqueue->Pop(&data); // 把任务从环形队列里取出
// 处理任务
std::cout << "消费完成,消费的数据是:" << data << std::endl;
}
}
int main()
{
srand((unsigned int)time(nullptr) ^ getpid());
// 创建一个环形队列(环形队列里可以是int也可以是任务Task)
RingQueue<int> *rq = new RingQueue<int>();
// RingQueue<Task> *rq = new RingQueue<Task>();
// 单生产,单消费
pthread_t p, c;
pthread_create(p, nullptr, ProducerRoutine, rq);
pthread_create(c, nullptr, ConsumerRoutine, rq);
pthread_join(p, nullptr);
pthread_join(c, nullptr);
delete rq;
return 0;
}
二、多生产多消费
1.环形队列的实现
上面的代码我们只维护了生产者和消费者之间的互斥和同步关系(三种关系中的一种)
为了实现多生产多消费,我们需要在去维护生产者和生产者,消费者和消费者之间的互斥关系
只要最终能保证:在任何时刻,只有一个生产者或一个消费者在临界区(环形队列)里 就行
为了维护 生产者和生产者/消费者和消费者 之间的互斥关系 -> 新增两把锁
① pthread_mutex_t _pmutex:保证只有一个生产者进入环形队列(多个生产者竞争出一个)
② pthread_mutex_t _cmutex:保证只有一个消费者进入环形队列(多个消费者竞争出一个)
总结: 维护生产者和消费者的关系 -> 信号量;维护生产者和生产者/消费者和消费者的关系 -> 锁
cpp
#pragma once
#include <iostream>
#include <vector>
#include <cassert>
#include <semaphore.h>
// 环形队列的默认大小
static const int gcap = 5;
// 设置成模版类型,队列中可以放任意类型的数据
template <class T>
class RingQueue
{
private:
// 封装系统调用sem_wait
void P(sem_t &sem);
// 封装系统调用sem_post
void V(sem_t &sem);
public:
RingQueue()
~RingQueue()
// 生产者向队列里放数据
void Push(const T &in);
// 消费者从队列中取数据
void Pop(T *out);
private:
std::vector<T> _queue; // 数组模拟环形队列
int _cap; // 环形队列的容量
sem_t _spaceSem; // 生产者 -> 空间资源
sem_t _dataSem; // 消费者 -> 数据资源
int _producerStep; // 生产者的位置(数组下标)
int _consumerStep; // 消费者的位置(数组下标)
pthread_mutex_t _pmutex; // 保证只有一个生产者进入队列
pthread_mutex_t _cmutex; // 保证只有一个消费者进入队列
};
对系统调用sem_wait()和sem_post()的封装不变,其他四个函数需要做调整
(1) RingQueue()
cpp
RingQueue(const int &cap = gcap)
: _queue(cap), _cap(cap)
{
// 初始化信号量
int n = sem_init(&_spaceSem, 0, _cap);
assert(n == 0);
n = sem_init(&_dataSem, 0, 0);
assert(n == 0);
// 初始化生产者和消费者的位置
_productorStep = _consumerStep = 0;
// 初始化锁
pthread_mutex_init(&_pmutex, nullptr);
pthread_mutex_init(&_cmutex, nullptr);
}
(2) ~RingQueue()
cpp
~RingQueue()
{
// 销毁信号量
sem_destroy(&_spaceSem);
sem_destroy(&_dataSem);
// 销毁锁
pthread_mutex_destroy(&_pmutex);
pthread_mutex_destroy(&_cmutex);
}
(3) void Push(const T &in);
生产者向环形队列里放数据
cpp
void Push(const T &in)
{
// 生产者要了一个空间,空间信号量--
P(_spaceSem);
// 选一个生产者放任务(加锁)
pthread_mutex_lock(&_pmutex);
// 把数据放进队列
_queue[_producerStep++] = in;
// 维护环状结构
_producerStep %= _cap;
// 放进队列后解锁
pthread_mutex_unlock(&_pmutex);
// 队列新增了数据,数据信号量++
V(_dataSem);
}
(4) void Pop(T *out);
消费者从环形队列中取数据
cpp
void Pop(T *out)
{
// 消费者拿了一个数据,数据信号量--
P(_dataSem);
// 选一个消费者拿任务(加锁)
pthread_mutex_unlock(&_cmutex);
// 把数据拿出队列
*out = _queue[_consumerStep++];
// 维护环状结构
_consumerStep %= _cap;
// 拿出队列后解锁
pthread_mutex_unlock(&_cmutex);
// 队列多出了空间,空间信号量++
V(_spaceSem);
}
2.上层调用逻辑
cpp
#include "RingQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
// 生产者线程
void *ProducerRoutine(void *rq)
{
// 通过参数rq拿到环形队列
RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);
// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);
// 生产活动
while (true)
{
int data = rand() % 10 + 1; // 模拟构建任务
ringqueue->Push(data); // 把任务放进环形队列
std::cout << "生产完成,生产的数据是:" << data << std::endl;
}
}
// 消费者线程
void *ConsumerRoutine(void *rq)
{
// 通过参数rq拿到环形队列
RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);
// RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);
// 消费活动
while (true)
{
int data;
ringqueue->Pop(&data); // 把任务从环形队列里取出
// 处理任务
std::cout << "消费完成,消费的数据是:" << data << std::endl;
}
}
int main()
{
srand((unsigned int)time(nullptr) ^ getpid());
// 创建一个环形队列(环形队列里可以是int也可以是任务Task)
RingQueue<int> *rq = new RingQueue<int>();
// RingQueue<Task> *rq = new RingQueue<Task>();
// 多生产,多消费
pthread_t p[4], c[8];
for (int i = 0; i < 4; i++)
pthread_create(p + i, nullptr, ProducerRoutine, rq);
for (int i = 0; i < 8; i++)
pthread_create(c + i, nullptr, ConsumerRoutine, rq);
for (int i = 0; i < 4; i++)
pthread_join(p[i], nullptr);
for (int i = 0; i < 8; i++)
pthread_join(c[i], nullptr);
delete rq;
return 0;
}