目录
这篇博客的重点在于代码实现,理论部分请看优快云
一、单生产单消费
1.阻塞队列的实现
将阻塞队列封装成一个类:首先给出整体框架,接着会说明每一个类内函数的实现
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
// 阻塞队列默认大小
const int gmaxcap = 5;
// 设置成模版类型
template <class T>
class BlockQueue
{
public:
BlockQueue()
~BlockQueue()
// 生产者向队列里放数据
void push(const T &in);
// 消费者从队列中取数据
void pop(T *out);
private:
// 判断队列是否为空
bool is_empty() { return _q.empty(); }
// 判断队列是否为满
bool is_full() { return _q.size() == _maxcap; }
private:
std::queue<T> _q; // 阻塞队列
int _maxcap; // 队列中元素的上限
pthread_mutex_t _mutex;// 锁(保证临界资源的安全)
pthread_cond_t _pcond; // 生产者对应的条件变量
pthread_cond_t _ccond; // 消费者对应的条件变量
};
① 将阻塞队列设置成模版类型,未来就可以往阻塞队列中放任意类型的数据
② pthread_mutex_t _mutex; -> 锁:保证访问阻塞队列是互斥的
③ pthread_cond_t _pcond; -> 生产者对应的条件变量(队列为满 让生产者休眠)
④ pthread_cond_t _ccond; -> 消费者对应的条件变量(队列为空 让消费者休眠)
(1) BlockQueue()
BlockQueue(const int &maxcap = gmaxcap)
: _maxcap(maxcap)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_pcond, nullptr);
pthread_cond_init(&_ccond, nullptr);
}
(2) ~BlockQueue()
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_pcond);
pthread_cond_destroy(&_ccond);
}
(3) void push(const T &in);
void push(const T &in)
{
// 加锁访问阻塞队列
pthread_mutex_lock(&_mutex);
// 1.判断:队列为满 让生产者休眠
while (is_full())// 细节2:充当条件判断的语法必须是while,不能用if
{
// 生产条件不满足,无法生产,让生产者进行等待
pthread_cond_wait(&_pcond, &_mutex);// 细节1:第二个参数是正在使用的互斥锁
}
// 2.走到这里一定没有满(可以生产)
_q.push(in);
// 3.绝对能保证,阻塞队列里面一定有数据
// 唤醒一个消费者 让他来消费
pthread_cond_signal(&_ccond);// 细节3:可以放在临界区内部,也可以放在外部
pthread_mutex_unlock(&_mutex);
}
① 细节1:pthread_cond_wait()函数的第二个参数,必须是我们正在使用的互斥锁!
② 细节2:充当条件判断的语法必须是while,不能用if -> 预防伪唤醒情况的发生
③ 细节3: pthread cond signal()函数 可以放在临界区内部,也可以放在临界区外部
(4) void pop(T *out);
void pop(T *out)
{
pthread_mutex_lock(&_mutex);
// 1.判断:队列为空 让消费者休眠
while (is_empty())
{
// 消费条件不满足,无法消费,让消费者进行等待
pthread_cond_wait(&_ccond, &_mutex);
}
// 2.走到这里一定不为空(可以消费)
*out = _q.front();
_q.pop();
// 3.绝对能保证,阻塞列里面至少有一个空位
// 唤醒一个生产者 让他来生产
pthread_cond_signal(&_pcond);
pthread_mutex_unlock(&_mutex);
}
2.上层调用逻辑
#include "BlockQueue.hpp"
#include <ctime>
#include <unistd.h>
// 消费者线程
void *consumer(void *bq_)
{
// 首先要通过参数bq_拿到阻塞队列
BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
// 消费活动
while (true)
{
int data;
bq->pop(&data) // 把任务从进阻塞队列里取出
// 处理任务
std::cout << "消费的数据: " << data << std::endl;
}
return nullptr;
}
// 生产者线程
void *producer(void *bq_)
{
// 首先要通过参数bq_拿到阻塞队列
BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
// 生产活动
while (true)
{
int data = rand() % 10 + 1 // 模拟构建任务
bq->push(); // 把任务放进阻塞队列
std::cout << "生产的数据: " << data << std::endl;
}
return nullptr;
}
int main()
{
srand((unsigned long)time(nullptr) ^ getpid());
// 创建一个阻塞队列
BlockQueues<int> *bq = new BlockQueues<int>();
pthread_t c, p;
pthread_create(&c, nullptr, consumer, (void *)bq);
pthread_create(&p, nullptr, producer, (void *)bq);
pthread_join(c, nullptr);
pthread_join(p, nullptr);
delete bq;
return 0;
}
二、多生产多消费
1.阻塞队列的实现
多生产多消费阻塞队列和上面实现的单生产单消费的一样,原因是什么?
多个生产者进入到push()时要竞争这把锁 -> 永远只有一个生产者能进到阻塞队列
多个消费者进入到pop()时要竞争这把锁 -> 所以永远只有一个消费者能进到阻塞队列
因为有锁的存在,任何一个时刻只能有一个执行流在阻塞队列里push()/pop()数据
所以不管外面有多少线程,真正能进入阻塞队列里生产或消费的线程永远只有一个
2.上层调用逻辑
逻辑上没有任何差别,只需要多创建几个线程即可
#include "BlockQueue.hpp"
#include <ctime>
#include <unistd.h>
// 消费者线程
void *consumer(void *bq_)
{
// 首先要通过参数bq_拿到阻塞队列
BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
// 消费活动
while (true)
{
int data;
bq->pop(&data) // 把任务从进阻塞队列里取出
// 处理任务
std::cout << "消费的数据: " << data << std::endl;
}
return nullptr;
}
// 生产者线程
void *producer(void *bq_)
{
// 首先要通过参数bq_拿到阻塞队列
BlockQueues<int> *bq = static_cast<BlockQueues<int> *>(bq_);
// 生产活动
while (true)
{
int data = rand() % 10 + 1 // 模拟构建任务
bq->push(); // 把任务放进阻塞队列
std::cout << "生产的数据: " << data << std::endl;
}
return nullptr;
}
int main()
{
srand((unsigned long)time(nullptr) ^ getpid());
// 创建一个阻塞队列
BlockQueues<int> *bq = new BlockQueues<int>();
// 创建多个线程
pthread_t c[2], p[3];
pthread_create(c, nullptr, consumer, (void *)bq);
pthread_create(c+1, nullptr, consumer, (void *)bq);
pthread_create(p, nullptr, producer, (void *)bq);
pthread_create(p+1, nullptr, producer, (void *)bq);
pthread_create(p+2, nullptr, producer, (void *)bq);
pthread_join(c[0], nullptr);
pthread_join(c[1], nullptr);
pthread_join(p[0], nullptr);
pthread_join(p[1], nullptr);
pthread_join(p[2], nullptr);
delete bq;
return 0;
}