目录
参考:《C++并发编程实战 第二版》Anthony Willams著 吴天明 译
在学习完这本书后想要提高一下编码能力。
可以先参考精简版的注释,尝试写一写代码,感觉有难度的话就参考详细版。
具体代码和个人遇到的一些问题可以参考我的下一篇博客。
精简版注释:
//线程安全的队列容器(精简版)
//实现线程安全的队列容器(通过封装一个queue)
//成员变量:
// 1.通过mutex互斥对pop、push操作加锁,从而实现“安全”
// 2.内部封装的queue,queue容器内部存储share_ptr,数据通过share_ptr间接存储好处:
// 将每个数据的share_ptr初始化放在push()中,wait_and_pop就算异常安全的
// 对share_ptr实例分配内存的操作可以脱离锁的保护,有利于增强性能
// 3.条件变量等待条件成立(pop之前必须先push)
//成员函数:
// 1.构造函数
// 2.push
// 3.pop 通过函数参数传递要pop的数据:
// 两种pop,try_pop立即返回,即使队列为空(通过返回值表示操作失败)
// wait_and_pop 一直等到有数据才pop
// 4.通过返回值传递要pop的数据
// 5.empty
//
最简易可行的线程池(精简版)
//成员变量:
// 1.监控异常的原子变量
// 2.线程安全的工作队列
// 3.工作线程
// 4.封装好的能够join线程的线程类
// 5.work_thread() 当线程池能正常工作时就一直循环执行任务
//公有成员:
// 1.构造函数
// thread_pool():将监控异常的原子变量设置为false,
// join_threads用工作线程vector初始化
// {
// }
// 2.析构函数
// {
// 将原子变量设置为true,避免线程池销毁后,工作线程还在循环申请任务
// }
// 3.提交线程函数(函数模板)
// {
// 把待处理的任务(函数)提交给任务队列
// }
//
详细版注释:
//线程安全的队列容器(详细版)
//实现线程安全的队列容器(通过封装一个queue)
//成员变量:
// 1.通过mutex互斥对pop、push操作加锁,从而实现“安全”
// 2.内部封装的queue,queue容器内部存储share_ptr,数据通过share_ptr间接存储好处:
// 将每个数据的share_ptr初始化放在push()中,wait_and_pop就算异常安全的
// 对share_ptr实例分配内存的操作可以脱离锁的保护,有利于增强性能
// 3.条件变量等待条件成立(pop之前必须先push)
//成员函数:
// 1.构造函数
// 2.push{
1.为数据创建share_ptr指针 //通过move移动操作降低开销
2.互斥上锁
3.队列push数据
4.条件变量通知有数据
}
// 3.pop 通过函数参数传递要pop的数据:
// bool try_pop(T& value) //立即返回,即使队列为空(通过返回值表示操作失败)
{ //pop操作都是要操作队列的,所以一开始就要上锁,没有操作能提取到互斥之外
1.互斥上锁
2.判断是否为空队列 if(空) return false;
else(不空) {
1.队头数据传给value //通过move移动操作降低开销
2.队列pop
3.return true;
}
}
// void wait_and_pop(T& value) //一直等到有数据才pop
{ //pop操作都是要操作队列的,所以一开始就要上锁,没有操作能提取到互斥之外
1.互斥上锁;
2.通过条件变量等待条件发生,通过lambda表达式保证即使被条件变量唤醒了,也要保证队列非空才能进行pop操作;
3.对头数据传给value;
4.队列pop
}
// 4.通过返回值传递要pop的数据:
std::shared_ptr<T> try_pop()
{ //pop操作都是要操作队列的,所以一开始就要上锁,没有操作能提取到互斥之外
1.互斥上锁;
2.判断是否为空队列 if(空) return nullptr;
else(不空){
1.临时变量取得队头数据
2.队列pop
3.return 临时变量
}
}
std::shared_ptr<T> wait_and_pop()
{ //pop操作都是要操作队列的,所以一开始就要上锁,没有操作能提取到互斥之外
1.互斥上锁
2.通过条件等待条件发生,通过lambda表达式保证即使被条件变量唤醒了,也要保证队列非空才能进行pop操作;
3.临时变量取得对头数据
4.队列pop
5.return 临时变量
}
// 5. bool empty() const
{ //函数声明为const 要求将mutex 声明为mutable
1.互斥上锁
2.返回 封装队列的empty();
}
//
最简易可行的线程池(详细版)
#ifndef _THREAD_POOL_EASY_HPP_
#define _THREAD_POOL_EASY_HPP_
#include "ThreadRAII" //join_threads
#include "threadsafe_queue" //
//最简单的线程池::工作线程数目固定,当有任务要处理时就把他放进任务队列
//所以需要一个任务队列,用threadsafe_queue来实现
//各线程从任务队列中领取任务
//工作线程存储在vector容器中,并被引用到join_threads中,进行统一的析构管理
//
//私有成员:
// 1.线程的启动可能抛出异常,所以需要一个原子变量进行标识,
// 当该变量false时,线程池正常工作
// 每当有异常抛出时,设置为true
// 线程池析构时,也设置为true,
// 保证线程池析构后工作线程不会继续循环尝试从工作队列中获取任务
// 2.线程安全的工作队列
// 3.使用vector容器存放工作线程
// 4.封装好的能够join线程的线程类join_threads 里面存的是工作线程vector的整个引用
// 5.work_thread() 当线程池能正常工作时就一直循环执行任务
// {
// while(线程池正常工作)
// {
// 临时变量用来接收任务
// if(工作队列能pop出任务)
// 执行任务;
// else
// 将cpu时间让给其他线程;
// }
// }
//公有成员:
// 1.构造函数
// thread_pool():将监控异常的原子变量设置为false,
// join_threads用工作线程vector初始化
// {
// 通过std::thread::hardware_concurrency()取得最大线程数
// try
// { //线程启动可能抛出异常,所以用try catch包住
// for(最大线程数)
// {
// 工作线程放到存放工作线程的vector里
// }
// }
// catch(...){
// 出现异常将原子变量设置为true;
// throw;
// }
// }
// 2.析构函数
// {
// 将原子变量设置为true,避免线程池销毁后,工作线程还在循环申请任务
// }
// 3.提交线程函数(函数模板)
// {
// 把待处理的任务(函数)提交给任务队列
// }
//
#endif
ThreadRAII.h
还需要封装thread类,使std::thread对象在所有路径皆不可联结:详细请参考本书第八章或effective modren C++ 条款37
#ifndef _THREADRAII_H_
#define _THREADRAII_H_
//通过封装thread类,使std::thread对象在所有路径皆不可联结
// 详细参见 effective modren C++ 条款37
#include <vector>
#include <thread>
class join_threads
{
private:
std::vector<std::thread>& threads;
public:
explicit join_threads(std::vector<std::thread>& threads_):
threads(threads_) {}
~join_threads(){
for(unsigned long i =0; i<threads.size(); i++){
if(threads[i].joinable()){
threads[i].join();
}else{
threads[i].detach();
}
}
}
};
#endif