✨阻塞队列代码——管理任务
✨代码
✨Log.h
✨Task.h
✨BlockQueue.h
🎭成员变量一览
✨反思总结
✨线程池——管理线程
✨结构介绍
✨代码
✨ThreadPool.h
✨反思总结
✨简化线程池
✨C++代码——thread库实现
✨C代码——pthread库实现
✨反思
🎭优秀文章参考
✨阻塞队列代码——管理任务
✨代码
✨Log.h
#pragma once
#include<iostream>
#include<mutex>
#include<thread>
using namespace std;
#include<stdio.h>
#include <direct.h> // Windows
enum
{
SCREEN,ONEFILE,CLASSIFY
};
enum
{
INFO = 10,DEBUG,MERROR,WARING,COUNT
};
class Log
{
mutex mtx;
Log() {}
public:
static Log log;
Log& operator=(const Log&) = delete;
Log(const Log&) = delete;
string GetFileName(int type)
{
string ret;
switch (type)
{
case INFO:
ret = "info.txt";
break;
case DEBUG:
ret = "debug.txt";
break;
case ERROR:
ret = "error.txt";
break;
case WARING:
ret = "waring.txt";
break;
case COUNT:
ret = "count.txt";
break;
}
return ret;
}
string GetType(int type)
{
string ret;
switch (type)
{
case INFO:
ret = "[ info ]";
break;
case DEBUG:
ret = "[ debug ]";
break;
case MERROR:
ret = "[ error ]";
break;
case WARING:
ret = "[ waring ]";
break;
case COUNT:
ret = "[ count ]";
break;
}
return ret;
}
void WriteToFile(int des,int type,const string& content)
{
string ret = GetType(type);
ret += content;
switch (des)
{
case SCREEN:
cout << ret << endl;
break;
case CLASSIFY:
string filename = GetFileName(type);
{
unique_lock<mutex> lg(mtx);
FILE* fd = fopen(filename.c_str(), "a");
char str[100];
snprintf(str, sizeof(str), "%s\n", ret.c_str());
fwrite(str, sizeof(char), strlen(str), fd);
fclose(fd);
}
break;
}
}
};
Log Log::log;
✨Task.h
#pragma once
#include<iostream>
#include<unordered_map>
#include<functional>
#include<stdio.h>
#include<string>
#include<exception>
#include<thread>
using namespace std;
#include"Log.h"
using _fun_t = function<double()>;
class Task
{
public:
// 引用捕获
//Task(int a=0, int b=0, char op='+') : _a(a), _b(b), _op(op), _ret()
//{
// _hash['+'] = [this] {
// _ret = (double)_a + _b;
// };
// _hash['-'] = [this] {
// _ret = (double)_a - _b;
// };
// _hash['*'] = [this] {
// _ret = (double)_a * _b;
// };
// _hash['/'] = [this] {
// if (_b == 0) _ret = 0;
// else _ret = (double)_a / _b;
// };
// _hash['%'] = [this] {
// if (_b == 0) _ret = 0;
// else _ret = _a % _b;
// };
//}
// 使用值捕获进行构造
Task(int a = 0, int b = 0, char op = '+') : _a(a), _b(b), _op(op), _ret()
{
// 使用参数捕捉并且带有返回值就可以解决问题了
// 现在就算是浅拷贝也可以
_hash['+'] = [=]()->double {
return (double)_a + _b;
};
_hash['-'] = [=]()->double {
return(double)_a - _b;
};
_hash['*'] = [=]()->double {
return (double)_a * _b;
};
_hash['/'] = [=]()->double {
if (_b == 0) return 0;
else return (double)_a / _b;
};
_hash['%'] = [=]()->double {
if (_b == 0) return 0;
else return _a % _b;
};
}
void run()
{
_ret = _hash[_op]();
Log::log.WriteToFile(CLASSIFY, INFO, "任务运行完成");
}
string Print() const
{
char ret[100];
snprintf(ret,sizeof(ret), "%d %c %d = ?", _a, _op, _b);
return ret;
}
string PrintResult()
{
char ret[100];
snprintf(ret, sizeof(ret), "%d %c %d = %.3f", _a, _op, _b, _ret);
return ret;
}
private:
int _a, _b;
char _op;
double _ret;
unordered_map<char, function<double()>> _hash;
};
✨BlockQueue.h
#pragma once
#include<queue>
#include<vector>
#include<condition_variable>
#include<thread>
#include<mutex>
using namespace std;
#include"Task.h"
const int DEFAULT_TASK_NUMS = 5;
template<class T>
class BlockQueue
{
public:
BlockQueue(int capacity = DEFAULT_TASK_NUMS)
:_capacity(capacity)
{
Log::log.WriteToFile(CLASSIFY, INFO, "阻塞队列初始化完成");
}
bool empty()
{
unique_lock<mutex> lg(mtx);
return _taskq.size() == 0;
}
bool full()
{
unique_lock<mutex> lg(mtx);
return _taskq.size() == _capacity;
}
void push(const T& t)
{
unique_lock<mutex> lg(mtx);
Log::log.WriteToFile(CLASSIFY, INFO, "开始放任务");
string ret = t.Print();
Log::log.WriteToFile(CLASSIFY, COUNT, ret );
while(_taskq.size() == _capacity)
_c_cond.wait(lg);
//t.Print();
_taskq.push(t);
Log::log.WriteToFile(CLASSIFY, INFO, "放任务完成");
Log::log.WriteToFile(CLASSIFY, INFO, "通知消费者");
_p_cond.notify_one();
Log::log.WriteToFile(CLASSIFY, INFO, "通知消费者完成");
}
void run()
{
// unique_lock<mutex> lg(mtx);
// Log::log.WriteToFile(CLASSIFY, INFO, "开始计算任务");
// while (_taskq.size() == 0)
// {
// Log::log.WriteToFile(CLASSIFY, INFO, "消费者等待激活");
// _p_cond.wait(lg);
// }
// Log::log.WriteToFile(CLASSIFY, INFO, "消费者已激活");
T& t = _taskq.front();
_taskq.pop();// 使用引用接收,这个位置不能先释放,释放了就没有了
T t = _taskq.front();
_taskq.pop();
// Log::log.WriteToFile(CLASSIFY, INFO, "***生产者正在进行计算任务***");
// t.run();
// string ret = t.PrintResult();
// Log::log.WriteToFile(CLASSIFY, COUNT, ret);
// Log::log.WriteToFile(CLASSIFY, INFO, "***生产者计算任务完成***");
//
// Log::log.WriteToFile(CLASSIFY, INFO, "计算任务结束");
// Log::log.WriteToFile(CLASSIFY, INFO, "唤醒生产者");
// _c_cond.notify_one();
}
void pop(T* t)
{
unique_lock<mutex> lg(mtx);
while (_taskq.size() == 0)
{
Log::log.WriteToFile(CLASSIFY, INFO, "消费者等待激活");
_p_cond.wait(lg);
}
// 执行的是Task对象的浅拷贝
*t = _taskq.front();// 这是个赋值,不是拷贝
_taskq.pop();
t->run();
_c_cond.notify_one();
}
private:
queue<T> _taskq;
int _capacity;
mutex mtx;
condition_variable _p_cond;// 生产者给消费者发信号
condition_variable _c_cond;// 消费者给生产者发信号
};
🎭成员变量一览
Task.h
成员变量 | 意义 |
---|---|
_a | 成员一 |
_b | 成员二 |
_op | 运算符 |
_ret | 最终结果 |
unordered_map<char, function<double()>> | 运算符到运算函数的映射 |
BlockQueue.h
成员变量 | 意义 |
---|---|
queue _taskq; | 任务队列 |
int _capacity; | 任务队列的最大容量 |
mutex mtx; | 锁 |
condition_variable _p_cond; | 生产者给消费者发信号 |
condition_variable _c_cond; | 消费者给生产者发信号 |
✨反思总结
-
为什么要使用
两个条件变量???
如果只有一个条件变量
当生产者和消费者同时访问_taskq
,最开始没有任何任务,消费者被阻塞
生产者生产任务,生产一个任务释放锁并且唤醒锁(自己唤醒自己)
如果生产者锁和条件变量的竞争能力每次都强于消费者
,当生产者生产满
的时候,由于生产者的竞争能力强于消费者,那么消费者就不可能得到条件变量和锁
,就会产生死锁
两个条件变量
当生产者生产一个就会通知消费者,生产者消费者等待的是不同的条件变量,就能比上面的要高效
阻塞队列两把锁是为了更好的控制互斥和同步 -
为什么只有一把锁???
他们访问的是同一个共享资源
,如果有两把锁
,他们还是可能产生同时访问的问题
一个锁为了控制互斥 -
Log文件需不需要加锁???
我们来分析Log输出的问题,在线程启动的时候
,Log创建的文件属于共享资源
,这个是必须要加锁
的吧 -
Task中lambda参数接收
的问题——引用捕捉or传值捕捉
引用捕捉
原则上,引用捕捉
,这种方式只能进行浅拷贝
,新的对象中存的还是之前的引用
传值捕捉
传值捕捉
又会面临一个问题,如何将计算出来的值进行保存呢???
最终使用有返回值的方式进行捕捉
并且我们现在就不需要担心什么深拷贝浅拷贝的问题了
,只要他们的两个计算成员和运算符对,他的计算结果使用自己的结果接收就行了
-
BlockQueue中拿任务
——引用接收or赋值接收
Task.h的run函数专门放出来两个代码段
引用接收
T& t = _taskq.front();
_taskq.pop();// 使用引用接收,这个位置不能先释放,释放了就没有了
这个代码的问题在于使用引用接收
之后立即释放
原来的空间,这样就造成了内存访问异常
传值接收
传值就是拷贝
,就能很好的避免内存访问冲突的问题
6. 一定要在最后回收线程
✨线程池——管理线程
✨结构介绍
一次性创建多个线程
,将线程统一管理起来——将许多鱼放入鱼塘
将任务使用队列管理起来
,每次生产任务都加入队列——将食物放到队列中
每次需要拿任务就从队列中拿
,然后放到线程池中,让多个线程抢一个任务——鱼抢鱼食儿
没有任务的时候,所有线程等待
;当有任务就唤醒——条件变量
注意
以上是形象上的理解,细节还是需要注意的
,他们在抢食物的时候,需要先抢到锁,然后才能去拿食物,也就是说,这里的抢的是锁
✨代码
✨ThreadPool.h
#pragma once
#include<iostream>
#include<queue>
#include<vector>
#include<string>
#include<functional>
using namespace std;
#include"Task.h"
#include"Log.h"
#include"BlockQueue.h"
const int DEFAULT_THREADS = 5;
//const int DEFAULT_TASK_NUMS = 5;
// 让线程池去阻塞队列中取任务
template<class T>
class ThreadPool
{
static ThreadPool<T> _threadpool;
ThreadPool(int threads_capacity = DEFAULT_THREADS, int block_capacity = DEFAULT_TASK_NUMS)
:_threads_capacity(threads_capacity),_blockq(block_capacity)
{
for (int i = 0; i < _threads_capacity; i++)
{
_threads.push_back(thread( bind(&ThreadPool<T>::run,this)));
}
}
//ThreadPool()
// :_threads_capacity(DEFAULT_THREADS), _blockq(DEFAULT_TASK_NUMS)
//{
// for (int i = 0; i < _threads_capacity; i++)
// {
// _threads.push_back(thread([this] {
// run();
// }));
// }
//}
public:
ThreadPool(const ThreadPool<T>& t) = delete;
ThreadPool& operator=(const ThreadPool<T>& t) = delete;
// 可以将析构函数私有,对自己这个类没有什么影响,但是在继承的时候就会发生错误
~ThreadPool()
{
for (auto& th : _threads)
th.join();
}
static ThreadPool<T>* GetInstance()
{
return &_threadpool;
}
void run()
{
while (1)
{
unique_lock<mutex> lg(_mutex);
Log::log.WriteToFile(CLASSIFY, INFO, "线程开始启动");
while (_blockq.empty())
_p_cond.wait(lg);
Log::log.WriteToFile(CLASSIFY, INFO, "线程获取任务");
T t;
_blockq.pop(&t);
Log::log.WriteToFile(CLASSIFY, INFO, "运行任务");
t.run();
string ret = t.PrintResult();
Log::log.WriteToFile(CLASSIFY, COUNT, ret);
_c_cond.notify_one();
}
}
void push(const T& t)
{
unique_lock<mutex> lg(_mutex);
while (_blockq.full())
_c_cond.wait(lg);
_blockq.push(t);
Log::log.WriteToFile(CLASSIFY, INFO, "线程池任务添加成功");
_p_cond.notify_one();
}
private:
//queue<T> _taskq;// 阻塞队列
BlockQueue<T> _blockq;
vector<thread> _threads;
int _threads_capacity;
mutex _mutex;
condition_variable _p_cond;// 消费者等待生产者
condition_variable _c_cond;// 生产者等消费者
};
template<class T>
ThreadPool<T> ThreadPool<T>::_threadpool;
✨反思总结
静态成员函数
可以访问类中的静态公有/私有成员变量
- 一定要在
最后回收线程
- 将
线程池
设计成单例模式
线程一定是在最开始的时候创建
出来的,并且创建的线程要运行起来,需要注意函数绑定时this参数的问题
✨简化线程池
为了应对面试的手撕线程池,需要掌握一个简化版的线程池
✨C++代码——thread库实现
tinythreadpool.hpp
#include<iostream>
#include<mutex>
#include<thread>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
class Task
{
public:
void run(){}
private:
};
class ThreadPool
{
ThreadPool(int thread_capacity = 5,int task_capacity = 5)
:_thread_capacity(thread_capacity),_task_capacity(task_capacity)
{
puts("创建所有线程");
for(int i=0;i<_thread_capacity;i++)
{
_threads.push_back(thread(bind(&ThreadPool::run,this)));
}
}
~ThreadPool()
{
for(auto& e:_threads)
{
e.join();
}
}
public:
ThreadPool(const ThreadPool& tp)=delete;
ThreadPool& operator=(const ThreadPool& tp) = delete;
static ThreadPool& GetInstance()
{
static ThreadPool tp;
return tp;
}
void run()
{
while(1)
{
unique_lock<mutex> lg(_mtx);
while(_q_task.size() == 0)
_p_cond.wait(lg);
Task t = _q_task.front();
_q_task.pop();
t.run();
puts("正在执行");
_c_cond.notify_one();
}
}
void push(const Task& t)
{
unique_lock<mutex> lg(_mtx);
while(_q_task.size() == _task_capacity)
_c_cond.wait(lg);
_q_task.push(t);
_p_cond.notify_one();
}
private:
queue<Task> _q_task;
vector<thread> _threads;
int _thread_capacity;// 线程容量
int _task_capacity;// 任务容量
mutex _mtx;
condition_variable _p_cond;// 生产者给消费者发消息
condition_variable _c_cond;// 消费者给生产者发消息
};
main.cc
#include<iostream>
using namespace std;
#include"tinythreadpool.hpp"
int main()
{
ThreadPool& tp = ThreadPool::GetInstance();
srand(time(nullptr));
while(1)
{
Task t;
puts("生产任务");
tp.push(t);
}
return 0;
}
✨C代码——pthread库实现
ThreadPool.h
#include<iostream>
#include<mutex>
#include<thread>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
#include<pthread.h>
class Task
{
public:
void run(){}
private:
};
class ThreadPool
{
ThreadPool(int thread_capacity = 5,int task_capacity = 5)
:_thread_capacity(thread_capacity),_task_capacity(task_capacity)
{
puts("创建所有线程");
// for(int i=0;i<_thread_capacity;i++)
// {
// _threads.push_back(thread(bind(&ThreadPool::run,this)));
// }
for(int i=0;i<_thread_capacity;i++)
{
pthread_t id;
// c++11中不能使用bind作为pthread_create的回调函数
// pthread_create(&id,nullptr,bind(&ThreadPool::run,this,placeholders::_1),nullptr);
pthread_create(&id,nullptr,ThreadPool::run,this);
_threads.push_back(id);
}
}
// ~ThreadPool()
// {
// for(auto& e:_threads)
// {
// e.join();
// }
// }
~ThreadPool()
{
for(auto& e :_threads)
{
pthread_join(e,nullptr);
}
}
public:
ThreadPool(const ThreadPool& tp)=delete;
ThreadPool& operator=(const ThreadPool& tp) = delete;
static ThreadPool& GetInstance()
{
// 懒汉模式
// 第一次调用时初始化
// c++11之后编译器保证值初始化一次
static ThreadPool tp;
return tp;
}
static void* run(void* args)
{
ThreadPool* tp = static_cast<ThreadPool*>(args);
while(1)
{
pthread_mutex_lock(&tp->_mutex);
while(tp->_q_task.size() == 0)
pthread_cond_wait(&tp->_p_cond,&tp->_mutex);
Task t = tp->_q_task.front();
tp->_q_task.pop();
t.run();
puts("正在执行");
pthread_cond_signal(&tp->_c_cond);
pthread_mutex_unlock(&tp->_mutex);
}
}
void push(const Task& t)
{
pthread_mutex_lock(&_mutex);
while(_q_task.size() == _task_capacity)
pthread_cond_wait(&_c_cond,&_mutex);
_q_task.push(t);
pthread_cond_signal(&_p_cond);
pthread_mutex_unlock(&_mutex);
}
// void run()
// {
// while(1)
// {
// unique_lock<mutex> lg(_mtx);
// while(_q_task.size() == 0)
// _p_cond.wait(lg);
// Task t = _q_task.front();
// _q_task.pop();
// t.run();
// puts("正在执行");
// _c_cond.notify_one();
// }
// }
// void push(const Task& t)
// {
// unique_lock<mutex> lg(_mtx);
// while(_q_task.size() == _task_capacity)
// _c_cond.wait(lg);
// _q_task.push(t);
// _p_cond.notify_one();
// }
private:
queue<Task> _q_task;
vector<pthread_t> _threads;
int _thread_capacity;// 线程容量
int _task_capacity;// 任务容量
pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t _p_cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t _c_cond = PTHREAD_COND_INITIALIZER;
};
✨反思
- c++11后,
懒汉模式不需要加锁
,编译器只会初始化静态变量一次
,保证了线程安全
static ThreadPool& GetInstance()
{
// 懒汉模式
// 第一次调用时初始化
// c++11之后编译器保证值初始化一次
static ThreadPool tp;
return tp;
}
- c++11规定,
不能使用bind作为pthread_create的回调函数
ThreadPool(int thread_capacity = 5,int task_capacity = 5)
:_thread_capacity(thread_capacity),_task_capacity(task_capacity)
{
puts("创建所有线程");
// for(int i=0;i<_thread_capacity;i++)
// {
// _threads.push_back(thread(bind(&ThreadPool::run,this)));
// }
for(int i=0;i<_thread_capacity;i++)
{
pthread_t id;
// c++11中不能使用bind作为pthread_create的回调函数
// pthread_create(&id,nullptr,bind(&ThreadPool::run,this,placeholders::_1),nullptr);
pthread_create(&id,nullptr,ThreadPool::run,this);
_threads.push_back(id);
}
}