标题:[多线程]基于环形队列(RingQueue)的生产者-消费者模型
@水墨不写bug

一、模型实现
接下来我们要实现一个基于环形队列(RingQueue)
的生产者-消费者模型
。该模型使用信号量和互斥锁来保证生产者和消费者之间的同步与互斥操作。
cpp
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
using std::cout;
using std::endl;
// 基于环形队列的生产消费模型
// 与加锁不同,加锁要求对象rq内部内嵌一个锁,而两个信号量
// (sem_t _data_sem; // 数据信号量
// sem_t _spase_sem; // 空间信号量)
// 可以做到完美维护以下的四条规则:
/*
1.生产者不会套消费者一圈
2.消费者不超过生产者
3.队列为空,消费者先消费
4.队列为满,生产者先生产
*/
template <typename T>
class RingQueue
{
private:
// 信号量(计数器)自减------申请信号量
void P(sem_t &psem)
{
sem_wait(&psem);
}
// 信号量(计数器)自增------归还信号量
void V(sem_t &vsem)
{
sem_post(&vsem);
}
public:
RingQueue(int maxcap)
: _ringqueue(maxcap), _maxcap(maxcap), _data_sem(), _space_sem(), _p_step(0), _c_step(0), _size(0)
{
// 初始化信号量
sem_init(&_data_sem, 0, 0);
sem_init(&_space_sem, 0, maxcap);
// 初始化互斥锁
pthread_mutex_init(&_p_mutex, nullptr);
pthread_mutex_init(&_c_mutex, nullptr);
}
// 生产者向环形队列插入数据
void Push(const T& in)
{
// 申请空间信号量
P(_space_sem);
pthread_mutex_lock(&_p_mutex);
_ringqueue[_p_step] = in;
_p_step++;
_p_step %= _maxcap;
_size++;
pthread_mutex_unlock(&_p_mutex);
V(_data_sem);
}
// 消费者从环形队列拿出数据
void Pop(T *out)
{
// 申请数据信号量
P(_data_sem);
pthread_mutex_lock(&_c_mutex);
*out = _ringqueue[_c_step];
_c_step++;
_c_step %= _maxcap;
_size--;
pthread_mutex_unlock(&_c_mutex);
V(_space_sem);
}
~RingQueue()
{
// 销毁信号量
sem_destroy(&_data_sem);
sem_destroy(&_space_sem);
}
size_t GetSize()
{
return _size;
}
private:
// 环形队列
std::vector<T> _ringqueue;
int _maxcap; // 最大数据个数
sem_t _data_sem; // 数据信号量
sem_t _space_sem; // 空间信号量
pthread_mutex_t _p_mutex; // 维护生产者之间关系的互斥锁
pthread_mutex_t _c_mutex; // 维护消费者之间关系的互斥锁
int _p_step; // 生产者位置
int _c_step; // 消费者位置
int _size; // 队列内数据个数
};
二、模型设计详解
1. 成员变量
std::vector<T> _ringqueue
:环形队列,用于存储数据。int _maxcap
:队列的最大容量。sem_t _data_sem
和sem_t _space_sem
:信号量,分别用于表示队列中的数据量和剩余空间量。(生产者和消费者关系的资源类型不同;生产者关心剩余空间资源,消费者关心剩余数据资源)pthread_mutex_t _p_mutex
和pthread_mutex_t _c_mutex
:互斥锁,分别用于保护生产者和消费者的操作。int _p_step
和int _c_step
:生产者和消费者在队列中的位置指针。int _size
:队列内数据的当前个数。
2. 构造函数
cpp
RingQueue(int maxcap)
: _ringqueue(maxcap), _maxcap(maxcap), _data_sem(), _space_sem(), _p_step(0), _c_step(0), _size(0)
{
// 初始化信号量
sem_init(&_data_sem, 0, 0);
sem_init(&_space_sem, 0, maxcap);
// 初始化互斥锁
pthread_mutex_init(&_p_mutex, nullptr);
pthread_mutex_init(&_c_mutex, nullptr);
}
- 初始化环形队列的最大容量。
- 初始化信号量,其中
_data_sem
的初始值为 0,表示初始时没有数据;_space_sem
的初始值为maxcap
,表示初始时队列可用空间为最大容量。 - 初始化互斥锁
_p_mutex
和_c_mutex
,分别用于维持生产者之间和消费者之间的互斥。
3. Push
函数
cpp
void Push(const T& in)
{
// 申请空间信号量
P(_space_sem);
pthread_mutex_lock(&_p_mutex);
_ringqueue[_p_step] = in;
_p_step++;
_p_step %= _maxcap;
_size++;
pthread_mutex_unlock(&_p_mutex);
V(_data_sem);
}
P(_space_sem)
:生产者申请空间信号量,如果队列已满(信号量为0),则生产者阻塞等待。pthread_mutex_lock(&_p_mutex)
:加锁保护生产者的操作。- 将数据插入到环形队列的当前位置
_p_step
,然后更新_p_step
和_size
。 pthread_mutex_unlock(&_p_mutex)
:解锁。V(_data_sem)
:释放数据信号量,通知消费者有新数据可用。
4. Pop
函数
cpp
void Pop(T *out)
{
// 申请数据信号量
P(_data_sem);
pthread_mutex_lock(&_c_mutex);
*out = _ringqueue[_c_step];
_c_step++;
_c_step %= _maxcap;
_size--;
pthread_mutex_unlock(&_c_mutex);
V(_space_sem);
}
P(_data_sem)
:消费者申请数据信号量,如果队列为空(信号量为0),则消费者阻塞等待。pthread_mutex_lock(&_c_mutex)
:加锁保护消费者的操作。- 从环形队列的当前位置
_c_step
取出数据,然后更新_c_step
和_size
。 pthread_mutex_unlock(&_c_mutex)
:解锁。V(_space_sem)
:释放空间信号量,通知生产者有新的可用空间。
5. 析构函数
cpp
~RingQueue()
{
// 销毁信号量
sem_destroy(&_data_sem);
sem_destroy(&_space_sem);
}
- 销毁信号量,释放资源。
6. GetSize
函数
cpp
size_t GetSize()
{
return _size;
}
- 返回队列的当前大小。
三、模型总结
上述代码实现了一个基于环形队列
的生产者-消费者模型,通过使用信号量
来控制生产者和消费者之间
的同步和互斥,通过互斥锁
来保证生产者之间
、消费者之间
的互斥。
信号量用于控制队列中的数据量和剩余空间量,互斥锁用于保护生产者和消费者的操作。通过这种方式,可以有效地避免资源竞争和死锁问题,实现了高效的生产和消费操作。
四、模型测试
以下给出了模型的测试逻辑代码,可以清晰的观察出单生产单消费,多生产多消费的情况:
cpp
#include "RingQueue.hpp"
#include <unistd.h>
#include <cstdlib>
void *Product(void *args)
{
RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
while (true)
{
// 1.生产数据
int tem = (rand() ^ pthread_self()) % 37;
// 2.加入环形队列(访问临界资源)
rq->Push(tem);
cout << "Product num : " << tem << endl;
sleep(1);
// usleep(100000);
}
return nullptr;
}
void *Consume(void *args)
{
RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
while (true)
{
// 1.拿到数据(访问临界资源)
int get;
rq->Pop(&get);
// 2.处理数据
cout << "Consumer get :" << get << endl;
//usleep(110000);
}
return nullptr;
}
void test1()
{
cout << "main thread started ---pid:" << getpid() << endl;
srand((unsigned int)time(nullptr));
RingQueue<int> *rq = new RingQueue<int>(10);
// 创建线程
pthread_t p, c;
pthread_create(&p, nullptr, Product, (void *)rq);
pthread_create(&c, nullptr, Consume, (void *)rq);
while (1)
{
cout << "队列内数据个数:" << rq->GetSize() << endl;
sleep(1);
}
// 等待线程
pthread_join(p, nullptr);
pthread_join(c, nullptr);
}
void test2()
{
cout << "main thread started ---pid:" << getpid() << endl;
srand((unsigned int)time(nullptr));
RingQueue<int> *rq = new RingQueue<int>(10);
// 创建多个生产者消费者
pthread_t p1, c1;
pthread_t p2, c2;
pthread_t p3, c3;
pthread_t p4, c4;
pthread_create(&p1, nullptr, Product, (void *)rq);
pthread_create(&p2, nullptr, Product, (void *)rq);
pthread_create(&p3, nullptr, Product, (void *)rq);
pthread_create(&p4, nullptr, Product, (void *)rq);
pthread_create(&c1, nullptr, Consume, (void *)rq);
pthread_create(&c2, nullptr, Consume, (void *)rq);
pthread_create(&c3, nullptr, Consume, (void *)rq);
pthread_create(&c4, nullptr, Consume, (void *)rq);
while (1)
{
cout << "队列内数据个数:" << rq->GetSize() << endl;
sleep(1);
}
// 等待线程
pthread_join(p1, nullptr);
pthread_join(p2, nullptr);
pthread_join(p3, nullptr);
pthread_join(p4, nullptr);
pthread_join(c1, nullptr);
pthread_join(c2, nullptr);
pthread_join(c3, nullptr);
pthread_join(c4, nullptr);
}
int main()
{
// 测试单生产单消费
// test1();
// 测试多生产多消费
test2();
return 0;
}
完~
转载请注明出处
