Task.h
#pragma once
//阻塞队列并不一定只能放一个一个的整数,还可以放任务。这里的任务是简单计算任务
#pragma once
#include <iostream>
#include <string>
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;
}
}
std::string formatArg()//返回任务的表达式
{
return std::to_string(_x) + _op + std::to_string(_y) + "=";//to_string函数
}
std::string formatRes()//返回任务的结果
{
return std::to_string(_result) + "(" + std::to_string(_exitCode) + ")";
}
~Task()
{
}
private:
int _x;
int _y;
char _op;
int _result;
int _exitCode;
};
BlockQueue.h
#pragma once
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gcap = 5;
// 不要认为,阻塞队列只能放整数字符串之类的
// 也可以放对象
// template <class T>
// class BlockQueue
// {
// public:
// BlockQueue(con st int cap = gcap):_cap(cap)
// {
// pthread_mutex_init(&_mutex, nullptr);
// pthread_cond_init(&_consumerCond, nullptr);
// pthread_cond_init(&_productorCond, nullptr);
// }
// bool isFull(){ return _q.size() == _cap; }
// bool isEmpty() { return _q.empty(); }
// void push(const T &in)
// {
// pthread_mutex_lock(&_mutex);
// // 细节1:一定要保证,在任何时候,进入临界区不能直接进行生产工作,而是需要先对队列的容量进行判断决定是否需要生产。都是符合条件,才进行生产
// while(isFull()) // 1. 我们只能在临界区内部,判断临界资源是否就绪!注定了我们在当前一定是持有锁的!
// //这边不能采用if(isFull())这种判断条件,这是为了防止误唤醒。比如由五个生产线程和一个消费线程,此时队列已经满了,消费线程消费一个之后把休眠的生产线程全部唤醒,这时候每个生产线程都要向队列种push数据,但是队列种只有一个空位置,这时候就容易出错,所以我们被唤醒之后还要再次进行while循环内判断队列此时是否为空才能进入push工作,入果队列满了就再次进行条件变量进行休眠。
// {
// // 2. 要让线程进行休眠等待,不能持有锁等待!
// // 3. 注定了,pthread_cond_wait要有锁的释放的能力!
// pthread_cond_wait(&_productorCond, &_mutex); // 我休眠(切换)了,我醒来的时候,在哪里往后执行呢?
// // 4. 当线程醒来的时候,注定了继续从临界区内部继续运行!因为我是在临界区被切走的!
// // 5. 注定了当线程被唤醒的时候,继续在pthread_cond_wait函数出向后运行,又要重新申请锁,申请成功才会彻底返回。也就是只有持有所得时候,这个函数才会继续向后运行。
// }
// // 没有满的,就要让他进行生产
// _q.push(in);
// // 加策略,生产者生产到什么情况了就可以对消费者队列进行唤醒。
// // if(_q.size() >= _cap/2)
// pthread_cond_signal(&_consumerCond);
// pthread_mutex_unlock(&_mutex);
// // pthread_cond_signal(&_consumerCond);jie'sui'o'o
// }
// void pop(T *out)
// {
// pthread_mutex_lock(&_mutex);
// while(isEmpty())
// {
// pthread_cond_wait(&_consumerCond, &_mutex);
// }
// *out = _q.front();
// _q.pop();
// // 加策略
// pthread_cond_signal(&_productorCond);
// pthread_mutex_unlock(&_mutex);
// }
// ~BlockQueue()
// {
// pthread_mutex_destroy(&_mutex);
// pthread_cond_destroy(&_consumerCond);
// pthread_cond_destroy(&_productorCond);
// }
// private:
// std::queue<T> _q;//队列用来存储数据的容器
// int _cap;//队列的容量上限
// // 为什么我们的这份代码,只用一把锁呢,根本原因在于,
// // 我们生产和消费访问的是同一个queue&&queue被当做整体使用!
// pthread_mutex_t _mutex; //这个队列一定是一个临界资源,因为他已经被多线程进行访问了,所以为了防止有人向队列放数据的同时又有人像队列中拿数据,在这里定义一个锁。
// pthread_cond_t _consumerCond; // 消费者对应的条件变量,空,wait,防止消费者加锁发现为空释放锁然后继续加锁,这种不断地循环造成的浪费。
// pthread_cond_t _productorCond; // 生产者对应的条件变量,满,wait,当队列生产满了的时候让生产队列等一下,让消费队列来消费了在去进行生产而不是保持无意义的空转。这也属于有生产者和消费者之间的一种同步关系
// };
// 不要认为,阻塞队列只能放整数字符串之类的
// 也可以放对象
template <class T>
class BlockQueue
{
public:
BlockQueue(const int cap = gcap) :_cap(cap)//初始化阻塞队列的容量
{
pthread_mutex_init(&_mutex, nullptr);//对所进行初始化
pthread_cond_init(&_consumerCond, nullptr);//对条件变量进行初始化
pthread_cond_init(&_productorCond, nullptr);
}
bool isFull() { return _q.size() == _cap; }
bool isEmpty() { return _q.empty(); }
void push(const T& in)
{
//向队列中push数据的第一件事是向整个队列中进行加锁工作,防止我打算生产的时候有人正在消费
pthread_mutex_lock(&_mutex);
// 细节1:一定要保证,在任何时候,都是符合条件,才进行生产
if (isFull()) // 1. 我们只能在临界区内部,判断临界资源是否就绪!因为临界资源是被多线程共享的,所以判断临界资源(本身也是对临界资源的一种访问)是否就绪就需要在临界区中(也就是加锁和解锁之间)进行,注定了我们在当前一定是持有锁的!
{
// 2. 要让线程进行休眠等待,不能持有锁等待!
// 3. 注定了,pthread_cond_wait要有锁的释放的能力!
pthread_cond_wait(&_productorCond, &_mutex); // 当前是满的,就不能够再生产了,于是再自己的条件变量下进行休眠,wait函数一执行就会释放锁。我休眠了(可以理解为线程被切换),我醒来的时候,在哪里往后执行呢?
// 4. 当线程醒来的时候,注定了继续从临界区内部继续运行!因为我是在临界区被切走的!
// 5. 注定了当线程被唤醒的时候,继续在pthread_cond_wait函数处向后运行,又要重新申请锁,申请成功才会彻底返回
}
// 队列没有满,就要让他进行生产
_q.push(in);//执行完该行代码可以保证队列为非空
// 加策略
//if(_q.size() >= _cap/2)
pthread_cond_signal(&_consumerCond);//生产者唤醒消费者,但是此时消费者是否休眠不得而知。
pthread_mutex_unlock(&_mutex);
// pthread_cond_signal(&_consumerCond);
}
void pop(T* out)
{
pthread_mutex_lock(&_mutex);
if (isEmpty())
{
pthread_cond_wait(&_consumerCond, &_mutex);
}
*out = _q.front();
_q.pop();//执行完该行代码至少可以保证队列没有满
// 加策略
pthread_cond_signal(&_productorCond);//消费者唤醒生产者,唤醒行为可以放在释放锁前面或者后面都可以
pthread_mutex_unlock(&_mutex);
}
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);//销毁锁
pthread_cond_destroy(&_consumerCond);//销毁条件变量
pthread_cond_destroy(&_productorCond);
}
private:
std::queue<T> _q;
int _cap;//队列的容量上限
// 为什么我们的这份代码,只用一把锁呢,根本原因在于,
// 我们生产和消费访问的是同一个queue&&queue被当做整体使用!
pthread_mutex_t _mutex; //不论是生产者还是消费者,两个访问的都是同一个队列,所以要避免两个访问同一份资源,为了防止有人向队列放数据的同时又有人像队列中拿数据,在这里定义一个锁。
pthread_cond_t _consumerCond; // 消费者对应的条件变量,空,wait,防止消费者加锁发现队列为空释放锁然后继续加锁···,这种不断地循环造成的浪费。
pthread_cond_t _productorCond; // 生产者对应的条件变量,满,wait
};
Main.cpp
//基于阻塞队列BlockingQueue的生产消费者模型,类似于管道。
//二种角色(生产和消费者)在1个交易场所(通常是缓冲区)维系下面三种关系。321原则。
//生产者与生产者的关系:互斥(当我往货架放东西的时候你不能来放,是一张更极端的竞争)
//消费者和生产者:同步,如果货架满了就先消费,反之先生产。生产者和消费者不用花太多时间去看货架上是否要空位或者由我需要购买的东西,也就是频繁最临界资源做检测,这会使得效率低下,所以必须维护同步关系。
//消费者和生产者不可能同时往货架放东西和拿东西,所以存在竞争关系,所以还要维护互斥关系。
//生产者和生产者:互斥关系。
//生产者和消费者在队列中是互斥的关系,那所谓的高效体现在哪里呢?生产者并不是知识将数据放入到队列中,还要考虑数据是如何产生的,当消费者从队列中拿数据的时候,生产者虽然没有办法向队列种放数据但是可以不断地通过它的方法从系统网络种拿到数据,这时候消费者拿数据生产者生产数据就可以做到同时进行了,而队列中缓存了一部分数据可以满足消费者消费。
//在任何一个时间点,都只能由一个生产者和一个消费者,那么多生产者和多消费者的意义在哪里呢?这样就可以让有的消费者在拿任务,有的消费者在处理任务。所以生产消费,模式的高效性并不是体现在队列这个缓冲区当中的。
#include "blockQueue.hpp"
#include "task.hpp"
#include <pthread.h>
#include <unistd.h>
#include <ctime>
// void *consumer(void *args)
// {
// BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
// while (true)
// {
// Task t;//这就是task类需要一个无参构造的原因
// // 1. 将数据从blockqueue中获取 -- 获取到了数据
// bq->pop(&t);
// t();
// // 2. 结合某种业务逻辑,处理数据! -- TODO
// std::cout << pthread_self() << " | consumer data: " << t.formatArg() << t.formatRes() << std::endl;
// }
// }
// void *productor(void *args)
// {
// BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
// std::string opers = "+-*/%";
// while (true)
// {
// // sleep(1);
// // 1. 先通过某种渠道获取数据
// int x = rand() % 20 + 1;
// int y = rand() % 10 + 1;
// char op = opers[rand() % opers.size()];
// // 2. 将数据推送到blockqueue -- 完成生产过程
// Task t(x, y, op);//构造任务
// bq->push(t);
// std::cout << pthread_self() << " | productor Task: " << t.formatArg() << "?" << std::endl;//展示生产者的任务
// }
// }
// int main()
// {
// srand((uint64_t)time(nullptr) ^ getpid());
// // BlockQueue<int> *bq = new BlockQueue<int>();
// BlockQueue<Task> *bq = new BlockQueue<Task>();
// // 单生产和单消费(只需要维护一种关系也就是生产者和消费者之间的关系) -> 多生产和多消费(维护三种关系)
// pthread_t c[2], p[3];
// pthread_create(&c[0], nullptr, consumer, bq);
// pthread_create(&c[1], nullptr, consumer, bq);
// pthread_create(&p[0], nullptr, productor, bq);
// pthread_create(&p[1], nullptr, productor, bq);
// pthread_create(&p[2], nullptr, productor, 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;
// }
void* consumer(void* args)
{
BlockQueue<int>* bq = static_cast<BlockQueue<int> *>(args);
while (true)
{
sleep(1);//休眠1s导致我们执行代码的时候队列一下子就被生产满了吗,然后就是消费一个生产一个。
int data = 0;
// 1. 将数据从blockqueue中获取 -- 获取到了数据
bq->pop(&data);
// 2. 结合某种业务逻辑,处理数据! -- TODO
std::cout << " consumer data: " << data << std::endl;
}
}
void* productor(void* args)
{
BlockQueue<int>* bq = static_cast<BlockQueue<int> *>(args);
while (true)
{
// sleep(1);
// 1. 先通过某种渠道获取数据
int data = rand() % 10 + 1;
// 2. 将数据推送到blockqueue -- 完成生产过程
bq->push(data);
std::cout << " productor Task: " << data << std::endl;
}
}
int main()
{
srand((uint64_t)time(nullptr) ^ getpid());
BlockQueue<int>* bq = new BlockQueue<int>();//这就是我们的交易场所,而这个类将做为参数传递到各个线程,从而让各个线程可以看到这个交易场所。
// 单生产和单消费(只需要维护一种关系也就是生产者和消费者之间的关系) -> 多生产和多消费(维护三种关系)
pthread_t c, p;
pthread_create(&c, nullptr, consumer, bq);
pthread_create(&p, nullptr, productor, bq);
pthread_join(c, nullptr);
pthread_join(p, nullptr);
delete bq;
return 0;
}