C++17手写通用线程池框架
文章目录
大家好,我是飞猪6。最近学习Linux并发编程制作了通用线程池框架。
解决了两个死锁等待和线程池运行及资源回收策略,同时实现C++17标准的Any上帝类核心部分,结合模板编程实现"万能基类"
项目介绍
作为五大池之一(内存池、连接池、线程池、进程池、协程池),线程池的应用非常广泛,不管是客户端程序,还是后台服务程序,都是提高业务处理能力的必备模块。有很多开源的线程池实现,虽然各自接口使用上稍有区别,但是其核心实现原理都是基本相同的。
知识背景
- 熟练基于C++ 11标准的面向对象编程
- 组合和继承、继承多态、STL容器、智能指针、函数对象、绑定器、可变参模板编程等。
- 熟悉C++11多线程编程
- thread、mutex、atomic、condition_variable、unique_lock等。
- C++17和C++20标准的内容
- C++17的any类型和C++20的信号量semaphore,项目上都我们自己用代码实现。
- 熟悉多线程理论
- 多线程基本知识、线程互斥、线程同步、原子操作、CAS等。
前置知识
并发和并行
CPU单核 CPU多核、多CPU 并发 单核上,多个线程占用不同的CPU时间片,物理上还是串行执行的,但是由于每个线程占用的CPU时间 片非常短(比如10ms),看起来就像是多个线程都在共同执行一样,这样的场景称作并发 (concurrent)。
并行
在多核或者多CPU上,多个线程是在真正的同时执行,这样的场景称作并行(parallel)。
多线程的优势
多线程程序一定就好吗?不一定,要看具体的应用场景:
IO密集型
无论是CPU单核、CPU多核、多CPU,都是比较适合多线程程序的
CPU密集型
- CPU单核
多线程存在上下文切换,是额外的花销,线程越多上下文切换所花费的额外时间也越多,倒不如一个线 程一直进行计算。
- CPU多核、多CPU
多个线程可以并行执行,对CPU利用率好。
线程池
线程的消耗
- 为了完成任务,创建很多的线程可以吗?线程真的是越多越好?
- 线程的创建和销毁都是非常"重"的操作
- 线程栈本身占用大量内存
- 线程的上下文切换要占用大量时间
- 大量线程同时唤醒会使系统经常出现锯齿状负载或者瞬间负载量很大导致宕机
线程池的优势
操作系统上创建线程和销毁线程都是很"重"的操作,耗时耗性能都比较多,那么在服务执行的过程中, 如果业务量比较大,实时的去创建线程、执行业务、业务完成后销毁线程,那么会导致系统的实时性能 降低,业务的处理能力也会降低。
线程池的优势就是(每个池都有自己的优势),在服务进程启动之初,就事先创建好线程池里面的线 程,当业务流量到来时需要分配线程,直接从线程池中获取一个空闲线程执行task任务即可,task执行 完成后,也不用释放线程,而是把线程归还到线程池中继续给后续的task提供服务。
fixed模式线程池
线程池里面的线程个数是固定不变的,一般是ThreadPool创建时根据当前机器的CPU核心数量进行指定。
cached模式线程池
线程池里面的线程个数是可动态增长的,根据任务的数量动态的增加线程的数量,但是会设置一个线程 数量的阈值(线程过多的坏处上面已经讲过了),任务处理完成,如果动态增长的线程空闲了60s还没 有处理其它任务,那么关闭线程,保持池中最初数量的线程即可。 线程同步 线程互斥 互斥锁mutex atomic原子类型 线程通信 条件变量 condition_variable 信号量 semaphore
项目设计
万能基类Any
/**
* @Description 上帝类 结合模板和基类机制 实现类似Java中的万能基类Object C++20才引入
* @Version 1.0.0
* @Date 2024/9/1 14:32
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#ifndef THREADPOOL_ANY_H
#define THREADPOOL_ANY_H
#include <iostream>
#include <memory>
/**
* 模板代码需要放在头文件的原因
* 1.实例化时机
* 模板不是具体的类型,模板的实际代码是在模板实例化时生成的,即当用特定的类型参数调用模板,编译器会生成相应的类或函数。
* 每个实例化需要单独的定义,因此模板定义必须在编译器可以访问的地方
*
* 2.链接模型
* C++的链接模型要求每个定义在不同的翻译单元中必须相同。如果你把模板的实现放在.cpp文件中,每次实例化会生产出不同的函数或类名,这会导致连接错误
*
* 3.一次定义规则
* C++中的一次定义规则指出,一个程序除了某些特殊情况外,每个命名实体必须只有一次定义。模板本身并不是一个实体,但可以在多个地方实例化,因此它的声明必须在程序中保存一致
*
* 4.编译时解析
* 模板的实例化在编译时完成,这意味着编译器需要能够看到模板的完整定义才能正确地生成代码。如果模板的定义在.cpp文件中,那么当需要在其他地方实例化这个模板时,编译器就无法访问到模板的定义。
*
* 5.内联函数
* 模板中的函数默认是内联的,这意味着编译器会在每个使用点处展开函数体。如果模板定义不在头文件中,那么内联函数将无法正常工作,因为每个翻译单元都将无法看到函数的定义。
*/
class Any {
public:
Any() = default;
~Any() = default;
Any(const Any &) = delete;
Any &operator=(const Any &) = delete;
Any(Any &&) = default;
Any &operator=(Any &&) = default;
// 这个构造函数让Any类型接收任意类型其他的数据
template<class T>
Any(T data) {
_base = std::make_unique<Derive<T>>(data);
}
// 这个方法能把Any对象里面存储的data数据取出来
template<class T>
T _cast() {
// 从_base找到它所指向的Derive对象 从它里面取data成员变量
// 基类指针->派生类指针 get()方法返回智能指针内部保存的原始指针
Derive<T> *pd = dynamic_cast<Derive<T>*>(_base.get());
if (pd == nullptr){
throw "type is unmatch";
}
return pd->_data;
}
private:
// 基类类型
class Base {
public:
// 通过基类指针或引用来删除一个派生类对象,并且希望确保派生类的析构函数被正确调用
// 基类析构函数不定义为虚函数 导致只调用基类的析构函数,而派生类的析构函数不会被调用,这会导致资源泄露
virtual ~Base() = default;
};
// 派生类对象
template<class T>
class Derive : public Base {
public:
Derive(T data) : _data(data) {
}
// 保存任意的其他类型
T _data;
};
// 定义基类指针 unique_ptr禁止拷贝和赋值
std::unique_ptr<Base> _base;
};
#endif //THREADPOOL_ANY_H
信号量类Semaphore
/**
* @Description 实现一个信号量类
* @Version 1.0.0
* @Date 2024/9/2 10:27
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#ifndef THREADPOOL_SEMAPHORE_H
#define THREADPOOL_SEMAPHORE_H
#include <mutex>
#include <atomic>
#include <condition_variable>
class Semaphore {
public:
explicit Semaphore(int limit = 0);
~Semaphore();
// 获取信号量资源 PV操作
void wait();
// 增加一个信号量资源
void post();
private:
std::mutex _mtx;
std::condition_variable _cond;
int _resLimit;
std::atomic_bool _isExit;
};
#endif //THREADPOOL_SEMAPHORE_H
/**
* @Description TODO
* @Version 1.0.0
* @Date 2024/9/2 10:27
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#include "../head/Semaphore.h"
Semaphore::Semaphore(int limit) : _resLimit(limit),_isExit(false) {}
void Semaphore::post() {
if (_isExit) return;
std::unique_lock<std::mutex> lock(_mtx);
_resLimit++;
// 通知其他线程 信号量资源+1
_cond.notify_all();
}
void Semaphore::wait() {
if (_isExit) return;
std::unique_lock<std::mutex> lock(_mtx);
// 等待信号量有资源 没有资源的话 会阻塞当前线程
_cond.wait(lock,[&]()->bool{
return _resLimit > 0;
});
_resLimit--;
}
Semaphore::~Semaphore() {
_isExit = true;
}
任务抽象类Task
/**
* @Description 任务抽象基类
* @Version 1.0.0
* @Date 2024/9/1 10:50
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#ifndef THREADPOOL_TASK_H
#define THREADPOOL_TASK_H
#include <iostream>
#include <thread>
#include <chrono>
#include "Any.h"
#include "Semaphore.h"
using uLong = unsigned long long;
class Task;
// 实现接收提交到线程池的task任务执行完后的返回值类型Result
class Result {
public:
Result(std::shared_ptr<Task> task, bool isValid = true);
~Result() = default;
// 获取任务执行完的返回值
void setVal(Any any);
// 用户调用获取task返回值
Any get();
private:
// 存储任务的返回值
Any _any;
// 线程通信的信号量
Semaphore _sem;
// 指向对应获取返回值的任务对象
std::shared_ptr<Task> _task;
// 返回值是否有效
std::atomic_bool _isValid;
};
class Task {
public:
// 用户可以自定义任意任务类型 从Task继承重写run方法 实现自定义任务处理
// virtual T run() = 0; 模板函数和虚函数不可一起使用
virtual Any run() = 0;
void exec();
void setResult(Result *res);
Task();
/**
* 基类析构函数采用虚函数
* 1.资源释放
* 当通过基类指针删除派生类对象时,如果基类的析构函数不是虚函数,则只会调用基类的析构函数,而不会调用派生类的析构函数
* 这样会导致派生类中分配的资源没有得到适当的清理 从而引发内存泄漏
*
* 2.多态性
* 虚析构函数保证了即使是从基类的指针或者引用来删除一个派生类的对象 也会调用正确的析构函数,即派生类析构优先调用再调用基类的析构函数
*/
~Task() = default;
private:
Result* _result;
};
// 用户自定义的任务
class MyTask : public Task{
public:
MyTask();
MyTask(uLong begin,uLong end);
// 怎么设计run函数的返回值 可以表示任意的类型
Any run() override;
private:
uLong _begin;
uLong _end;
};
#endif //THREADPOOL_TASK_H
/**
* @Description TODO
* @Version 1.0.0
* @Date 2024/9/1 10:50
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#include "../head/Task.h"
void Task::exec() {
// 这里发生多态并把任务的
// 把任务的返回值setVal给Result
if (_result != nullptr){
_result->setVal(run());
}
}
void Task::setResult(Result *res) {
_result = res;
}
Task::Task() : _result(nullptr) {}
Result::Result(std::shared_ptr<Task> task, bool isValid) : _task(task), _isValid(isValid) {
_task->setResult(this);
}
Any Result::get() {
if (!_isValid){
return "";
}
// task任务没有执行完 这里阻塞用户线程
_sem.wait();
return std::move(_any);
}
void Result::setVal(Any any) {
// 存储task的返回值
this->_any = std::move(any);
// 已经获取了任务的返回值 增加信号量资源
_sem.post();
}
Any MyTask::run() {
std::cout << "tid:" << std::this_thread::get_id() << " 开始运行任务!" << std::endl;
//std::this_thread::sleep_for(std::chrono::seconds(2));
uLong sum = 0;
for (uLong i = _begin; i < _end; ++i) {
sum += i;
}
std::cout << "tid:" << std::this_thread::get_id() << " 任务运行结束!" << std::endl;
return sum;
}
MyTask::MyTask(uLong begin, uLong end) : _begin(begin),_end(end){
}
MyTask::MyTask() {
}
线程类型类Thread
/**
* @Description 线程类型
* @Version 1.0.0
* @Date 2024/9/1 10:48
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#ifndef THREADPOOL_THREAD_H
#define THREADPOOL_THREAD_H
#include <iostream>
#include <functional>
#include <thread>
class Thread {
public:
// 线程函数对象类型
using ThreadFunc = std::function<void(int)>;
// 线程构造
Thread(ThreadFunc func);
// 线程析构
~Thread();
// 启动线程
void start();
// 获取线程id
int getId() const;
private:
ThreadFunc _func;
static int _generateId;
// 保存线程id 自定义的id用来取vector容器中的thread对象进行回收匹配
int _threadId;
};
#endif //THREADPOOL_THREAD_H
/**
* @Description TODO
* @Version 1.0.0
* @Date 2024/9/1 10:48
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#include "../head/Thread.h"
void Thread::start() {
// 创建一个线程来执行一个线程函数
std::thread t(_func,_threadId);
// 设置分离线程 否则出了作用域线程就结束
t.detach();
}
// _generatedId从0开始
Thread::Thread(Thread::ThreadFunc func) : _func(func),_threadId(_generateId++){
}
Thread::~Thread() {
}
int Thread::getId() const {
return _threadId;
}
int Thread::_generateId = 0;
线程池类threadPool
/**
* @Description 线程池类
* @Version 1.0.0
* @Date 2024/9/1 10:48
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#ifndef THREADPOOL_THREADPOOL_H
#define THREADPOOL_THREADPOOL_H
#include <vector>
#include <queue>
#include <memory>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include "Thread.h"
#include "Task.h"
// 任务队列阈值上限
const int TASK_MAX_THRESHHOLD = INT32_MAX;
// 线程阈值上限
const int THREAD_MAX_THRESHHOLD = 100;
// 线程最大空闲等待时间 60s
const int THREAD_MAX_IDLE_TIME = 60;
// 线程池支持的模式
enum class PoolMode{
// 固定数量的线程
MODE_FIXED,
// 线程数量可动态增长
MODE_CACHED,
};
class threadPool {
public:
// 线程池构造
threadPool();
// 线程池析构
~threadPool();
threadPool(const threadPool&) = delete;
threadPool &operator=(const threadPool&) = delete;
// 设置线程池工作模式
void setMode(PoolMode mode);
// 设置初始的线程数量
void setInitThreadSize(int size);
// 设置task任务队列上限阈值
void setTaskQueMaxThreshHold(int threshHold);
// 给线程池提交任务 用户调用此接口 传入对象生产任务
Result submitTask(std::shared_ptr<Task> sp);
// 开启线程池
void start(int initThreadSize = std::thread::hardware_concurrency());
// 设置线程池cached模型下线程池阈值
void setThreadSizeThreshHold(int threshHold);
private:
// 线程池定义线程函数 线程池的所有线程从任务队列中消费任务
void threadFunc(int threadId);
// 检查pool的运行状态
bool checkRunningState() const;
private:
// 线程列表
// std::vector<Thread*> _threads;
// std::vector<std::unique_ptr<Thread>> _threads;
std::unordered_map<int,std::unique_ptr<Thread>> _threads;
// 初始的线程数量
int _initThreadSize;
// 记录空闲线程的数量
std::atomic_int _idleThreadSize;
// 线程数量上限阈值
int _threadSizeThreshHold;
// 线程池中的线程总数量
std::atomic_int _curThreadSize;
// 任务队列
std::queue<std::shared_ptr<Task>> _taskQue;
// 任务的数量
std::atomic_int _taskSize;
// 任务队列阈值上限
int _taskQueMaxThreshHold;
// 保障任务队列的线程安全
std::mutex _taskQueMtx;
// 表示任务队列不满
std::condition_variable _notFull;
// 表示任务队列不空
std::condition_variable _notEmpty;
// 等待线程资源全部回收
std::condition_variable _exitCond;
// 当前线程池的工作模式
PoolMode _poolMode;
// 当前线程池的启动状态
std::atomic_bool _isPoolRunning;
};
#endif //THREADPOOL_THREADPOOL_H
/**
* @Description TODO
* @Version 1.0.0
* @Date 2024/9/1 10:48
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#include "../head/threadPool.h"
threadPool::threadPool() : _initThreadSize(4),
_taskSize(0),
_taskQueMaxThreshHold(TASK_MAX_THRESHHOLD),
_poolMode(PoolMode::MODE_FIXED),
_isPoolRunning(false),
_idleThreadSize(0),
_threadSizeThreshHold(THREAD_MAX_THRESHHOLD),
_curThreadSize(0) {
}
threadPool::~threadPool() {
_isPoolRunning = false;
// 等待线程池中的所有线程返回 有两种状态 阻塞&正在执行任务
std::unique_lock<std::mutex> lock(_taskQueMtx);
_notEmpty.notify_all();
_exitCond.wait(lock, [&]() -> bool {
return _threads.size() == 0;
});
}
void threadPool::setMode(PoolMode mode) {
if (checkRunningState()) return;
_poolMode = mode;
}
void threadPool::setInitThreadSize(int size) {
_initThreadSize = size;
}
void threadPool::setTaskQueMaxThreshHold(int threshHold) {
if (checkRunningState()) return;
_taskQueMaxThreshHold = threshHold;
}
Result threadPool::submitTask(std::shared_ptr<Task> sp) {
// 获取锁
std::unique_lock<std::mutex> lock(_taskQueMtx);
// 线程的通信 等待任务队列有空余
//while (_taskQue.size() == _taskQueMaxThreshHold){
// _notFull.wait(lock);
//}
// 用户提交任务 最长不能阻塞超过1s 否则判断提交任务失败 返回
if (!_notFull.wait_for(lock, std::chrono::seconds(1),
[&]() -> bool {
return _taskQue.size() < (size_t) _taskQueMaxThreshHold;
})) {
// 表示_notFull等待1s 条件依然没有满足
std::cerr << "task queue is full,submit task failed" << std::endl;
// return task->getResult(); 线程执行完task task对象就被析构了
// 随着task被执行完 task对象也没了(智能指针)
return Result(sp, true);
}
// 如果有空余 把任务放入任务队列
_taskQue.emplace(sp);
_taskSize++;
// 新放了任务 所以任务队列一定不为空
_notEmpty.notify_all();
// cached模式适用场景:小而快的任务 需要根据任务数量和空闲线程的数据 判断是否需要创建新的线程
if (_poolMode == PoolMode::MODE_CACHED && _taskSize > _idleThreadSize && _curThreadSize < _threadSizeThreshHold) {
std::cout << "新线程id = " << std::this_thread::get_id() << " 被创建了" << std::endl;
// 创建新线程对象
auto ptr = std::make_unique<Thread>(std::bind(&threadPool::threadFunc, this, std::placeholders::_1));
int threadId = ptr->getId();
_threads.emplace(threadId, std::move(ptr));
// 启动线程
_threads[threadId]->start();
// 修改线程个数相关的变量
_curThreadSize++;
_idleThreadSize++;
}
// 返回任务的Result对象
return Result(sp);
}
void threadPool::start(int initThreadSize) {
// 设置线程池的启动状态
_isPoolRunning = true;
// 设置初始线程数量和初始线程总数量
setInitThreadSize(initThreadSize);
_curThreadSize = initThreadSize;
// 创建线程对象
for (int i = 0; i < _initThreadSize; ++i) {
// 创建thread线程对象的时候 把线程函数给thread线程对象
auto ptr = std::make_unique<Thread>(std::bind(&threadPool::threadFunc, this, std::placeholders::_1));
// unique_ptr指向唯一对象 不允许拷贝构造和赋值
// std::move 用于将一个对象标记为可以被移动的,而不是拷贝 将资源从一个对象“移动”到另一个对象,而不是进行昂贵的拷贝操作
int threadId = ptr->getId();
// _threads.emplace_back(std::move(ptr));
_threads.emplace(threadId, std::move(ptr));
}
// 启动所有线程 std::vector<Thread*> _threads;
for (int i = 0; i < _initThreadSize; ++i) {
// 启动线程 执行线程函数
_threads[i]->start();
// 记录初始空闲线程的数量
_idleThreadSize++;
}
}
void threadPool::threadFunc(int threadId) {
// std::cout << "begin threadFunc tid:" << std::this_thread::get_id() << std::endl;
// std::cout << "end threadFunc tid:" << std::this_thread::get_id() << std::endl;
// 记录线程开始运行的时间
auto lastTime = std::chrono::high_resolution_clock().now();
// 所有任务执行完成 线程池才可以回收资源
for (;;) {
std::shared_ptr<Task> task;
// unique_lock 它的主要功能是确保在作用域结束时,自动释放锁,以防止锁泄露(即没有及时释放锁导致的死锁问题)
// 收到队列不空的通知进行任务消费 出作用域要释放锁 让其他线程抢占锁
{
// 先获取锁
std::unique_lock<std::mutex> lock(_taskQueMtx);
std::cout << "tid:" << std::this_thread::get_id() << " 尝试获取任务..." << std::endl;
// cached模型下 有可能已经创建了很多线程 但是空闲时间超过60s 应该把多余线程资源回收
// 超过_initThreadSize数量的线程要进行回收
// 当前时间 - 上一次线程执行的时间 > 60s
// 每一秒钟返回一次 怎么区分 超时返回还是有任务待执行返回
// 锁 + 双重判断
while (_taskQue.size() == 0) {
// 线程池结束 回收线程资源
if (!_isPoolRunning) {
_threads.erase(threadId);
std::cout << "真实线程id = " << std::this_thread::get_id() << " 被回收了" << std::endl;
_exitCond.notify_all();
// 线程函数结束 线程结束
return;
}
if (_poolMode == PoolMode::MODE_CACHED) {
// 条件变量超时返回
if (std::cv_status::timeout == _notEmpty.wait_for(lock, std::chrono::seconds(1))) {
auto now = std::chrono::high_resolution_clock().now();
auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);
// 线程存活超过60s
if (dur.count() >= THREAD_MAX_IDLE_TIME && _curThreadSize > _initThreadSize) {
// 开始回收线程
// 修改记录线程数量的值
// 把线程对象从列表容器中删除 没有办法匹配列表取出的是那个thread对象
_threads.erase(threadId);
_curThreadSize--;
_idleThreadSize--;
std::cout << "真实线程id = " << std::this_thread::get_id() << " 超时被回收了" << std::endl;
return;
}
}
} else {
// 等待_notEmpty条件
_notEmpty.wait(lock);
}
// 线程池结束回收线程资源
if (!_isPoolRunning) {
_threads.erase(threadId);
std::cout << "真实线程id = " << std::this_thread::get_id() << " 超时被回收了" << std::endl;
_exitCond.notify_all();
return;
}
}
// 消费一个线程去执行任务 空闲线程数-1
_idleThreadSize--;
std::cout << "tid:" << std::this_thread::get_id() << " 获取任务成功..." << std::endl;
// 从任务队列取出一个任务
task = _taskQue.front();
_taskQue.pop();
_taskSize--;
// 如果依然有剩余任务 继续通知其他的线程任务进行消费
if (_taskQue.size() > 0) {
_notEmpty.notify_all();
}
// 执行完一个任务 进行通知可以继续提交生产任务
_notFull.notify_all();
// 出作用域当前线程释放锁
}
// 当前线程负责执行这个任务
if (task != nullptr) {
// 1.执行任务 2.把任务的返回值setVal方法给Result
// task->run();
task->exec();
}
// 任务执行完毕 空闲线程数量+1
_idleThreadSize++;
// 更新线程执行完任务的调度时间
lastTime = std::chrono::high_resolution_clock().now();
}
// _threads.erase(threadId);
// std::cout << "线程id = " << std::this_thread::get_id() << " 被回收了" << std::endl;
// _exitCond.notify_all();
}
bool threadPool::checkRunningState() const {
return _isPoolRunning;
}
void threadPool::setThreadSizeThreshHold(int threshHold) {
if (checkRunningState()) return;
if (_poolMode == PoolMode::MODE_CACHED) {
_threadSizeThreshHold = threshHold;
}
}
主函数测试代码
/**
* @Description 测试线程池
* @Version 1.0.0
* @Date 2024/8/31 20:27
* @Github https://github.com/Programmer-Kenton
* @Author Kenton
*/
#include "head/threadPool.h"
int main() {
#if 0
threadPool pool;
pool.start(4);
// 主线程等待5秒
std::this_thread::sleep_for(std::chrono::seconds(5));
#endif
#if 0
threadPool pool;
pool.start(4);
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
getchar();
#endif
#if 0
threadPool pool;
pool.start(4);
// 如何设计Result机制
Result res = pool.submitTask(std::make_shared<MyTask>(1,100000));
Result res2 = pool.submitTask(std::make_shared<MyTask>(100000,10000000));
Result res3 = pool.submitTask(std::make_shared<MyTask>(10000000,100000000000));
// 如果submitTask没有执行完 res.get()应该阻塞
// 返回一个Any类型 如何转换成具体类型
uLong sum = res.get()._cast<uLong>();
// 测试死锁 打印sum
// std::cout << "测试打印 sum = " << sum << std::endl;
uLong sum2 = res.get()._cast<uLong>();
// std::cout << "测试打印 sum2 = " << sum2 << std::endl;
uLong sum3 = res.get()._cast<uLong>();
// Master - Slave线程模型
// Master线程用来分解任务 然后给各个Slave线程分配
// 等待各个Slave线程执行完任务返回结果
// Master线程合并各个结果 输出
std::cout << sum + sum2 + sum3 << std::endl;
#endif
{
threadPool pool;
// 用户自己设置线程池的工作模式
pool.setMode(PoolMode::MODE_CACHED);
// 开始启动线程池
pool.start(4);
Result res1 = pool.submitTask(std::make_unique<MyTask>(1, 100000000));
Result res2 = pool.submitTask(std::make_unique<MyTask>(100, 100000000));
Result res3 = pool.submitTask(std::make_unique<MyTask>(10000, 100000000));
Result res4 = pool.submitTask(std::make_unique<MyTask>(100000, 100000000));
Result res5 = pool.submitTask(std::make_unique<MyTask>(1, 123456789));
uLong sum1 = res1.get()._cast<uLong>();
uLong sum2 = res2.get()._cast<uLong>();
// uLong sum3 = res2.get()._cast<uLong>();
// uLong sum4 = res2.get()._cast<uLong>();
// uLong sum5 = res2.get()._cast<uLong>();
std::cout << "sum1 = " << sum1 << std::endl;
std::cout << "sum2 = " << sum2 << std::endl;
// std::cout << "sum3 = " << sum3 << std::endl;
// std::cout << "sum4 = " << sum4 << std::endl;
// std::cout << "sum5 = " << sum5 << std::endl;
}
std::cout << "main over" << std::endl;
getchar();
}
Linux平台制作并使用动态库测试
1.打包动态库
进入到工作目录,执行打包命令,这里我们使用c++20标准,这里的cpp文件编写时指明了…/head文件下的头,因此不需要再指明头文件路径
g++ -fPIC -shared -o libtdpool.so ./source/*.cpp -std=c++20
2.配置全局头文件
把当前head目录下的头文件迁移到/usr/local/include目录下
mv /head/*.cpp /usr/local/include
3.配置全局动态库链接并测试
1.把我们创建好的动态库移动到/usr/local/lib目录下
mv libtdpool.so /usr/local/lib
2.查看ld.so.conf文件
root@kenton:/etc/ld.so.conf.d# cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
3.可知在ld.so.conf.d文件中添加配置文件mylib.conf并加入下面的内容
root@kenton:/etc/ld.so.conf.d# cd /etc/ld.so.conf.d/
root@kenton:/etc/ld.so.conf.d# ls
fakeroot-x86_64-linux-gnu.conf libc.conf mylib.conf x86_64-linux-gnu.conf
root@kenton:/etc/ld.so.conf.d# cat mylib.conf
/usr/local/lib
4.刷新配置
ldconfig
5.生成可执行程序
root@kenton:/usr/local/WorkSpace/threadPool# pwd
/usr/local/WorkSpace/threadPool
root@kenton:/usr/local/WorkSpace/threadPool# ls
CMakeLists.txt head libtdpool.so main02.cpp main.cpp source upgrade
root@kenton:/usr/local/WorkSpace/threadPool# g++ main.cpp -o main -ltdpool -lpthread
root@kenton:/usr/local/WorkSpace/threadPool# ls
CMakeLists.txt head libtdpool.so main main02.cpp main.cpp source upgrade
root@kenton:/usr/local/WorkSpace/threadPool#
6.测试代码 这里采用cached模式
项目输出到简历上
- 高并发网络服务器
- master-slave线程模型
- 耗时任务处理
项目名称:基于可变参模板实现的线程池
平台工具:CLion2024.2开发,Ubuntu22 g++编译so库,gdb调试分析定位死锁问题
项目描述:
1、基于可变参模板编程和引用折叠原理,实现线程池submitTask接口,支持任意任务函数和任意参数 的传递
2、使用future类型定制submitTask提交任务的返回值
3、使用map和queue容器管理线程对象和任务
4、基于条件变量condition_variable和互斥锁mutex实现任务提交线程和任务执行线程间的通信机制
5、支持fixed和cached模式的线程池定制
项目问题:
1、在ThreadPool的资源回收,等待线程池所有线程退出时,发生死锁问题,导致进程无法退出
2、在windows平台下运行良好的线程池,在linux平台下运行发生死锁问题,平台运行结果有差异化 分析定位问题: 主要通过gdb attach到正在运行的进程,通过info threads,thread tid,bt等命令查看各个线程的调用 堆栈信息,结合项目代码,定位到发生死锁的代码片段,分析死锁问题发生的原因以及最终的解决方案