线程池功能、原理以及C++线程池的极简实现
博主写博客最讨厌写的很麻烦了,博主会抛弃很多错误判断、一些不涉及主体的功能,让读者很容易读懂原理,读者可以根据博主的代码进行时间复杂度优化以及代码优化,毕竟是教学博主,也不喜欢秀技啦,秀的读者看不懂就偏移教学的本质啦。还想看啥的可以评论博主,博主来给你们写博客
线程池的功能
- 线程在创建和销毁的时候会消耗大量的时间,也加大了编码的难度,线程池是将一个个线程函数变成了任务,这些任务会加入队列,线程池会一个个的取出任务去做处理,这样的话会节省大量的不必要消耗,也能减少编码难度
线程池的原理
基本数据结构
using thrdpool_t_func_ptr = void* (*)(void*); //任务函数指针
class func {
public:
func() {
key = func_key_auto++;
func_state = 0;
}
public:
//任务函数
thrdpool_t_func_ptr exec_func;
//函数参数
void* arg;
//0为未执行,1 为正在执行,2 为正常退出
short func_state;
//函数返回值
void* returnValue;
//函数唯一识别码
long long key;
//函数识别码创建
static std::atomic<long long> func_key_auto;
};
using func_t = func; //线程状态
-
构造函数创建指定数量线程,并且线程一直执行thrdExecFunc函数,thrdExecFunc在第三步骤有展示
ThrdPool::ThrdPool(int n) { if (n < 1)n = 1; size = n; exec_length = 0; exec_func_num = 0; thrd_arr = new pthread_t[n]; //创建空转函数实例 for (int i = 0;i < n;i++) { pthread_create(&thrd_arr[i], nullptr, thrdExecFunc, this); } } -
通过调用添加任务接口向线程池加入任务,任务接口格式如下
func_t* thrdpoolPost(thrdpool_t_func_ptr func_p, void* _arg);
-
线程池在无任务状态下运行下面的函数,对队列加锁后取出任务的函数指针并加以运行,若无任务存在则挂起线程 5000 纳秒
void* ThrdPool::thrdExecFunc(void* data) { ThrdPool* p = (ThrdPool*)data; //data 为 this 指针 while (true) { p->k.lock(); //加锁 if (!p->funcQueue.empty()) { //当队列不为空时 func_t* func_exec = p->funcQueue.front(); //从队列顶部取出一个函数指针(任务) p->funcQueue.pop(); //弹出队列的函数指针 func_exec->func_state = 1; //将任务状态改为执行中 p->exec_func_num++; //正在执行的任务量+1 exec_func_num是原子变量就不加锁了 p->k.unlock(); //解锁 func_exec->returnValue = func_exec->exec_func(func_exec->arg); //将函数返回值加入 func_exec p->k.lock(); //加锁 func_exec->func_state = 2; //将任务状态改为执行结束 p->funcList.push_back(func_exec); //将任务状态加入列表中,可以根据此状态获取返回值 p->k.unlock(); //解锁 p->exec_func_num--; //正在执行任务量-1 } else { //当队列为空时 p->k.unlock(); //解锁 usleep(5000); //等待 5000 纳秒防止 CPU 空转 } } return nullptr; }
- 以上的线程池的基本原理,不懂的小伙伴可以多看两遍
下面是完整代码展示
-
目录结构
. ├── CMakeLists.txt ├── build └── src ├── main.cpp ├── thrdpool.cpp └── thrdpool.h -
编译、执行命令
cd build cmake .. make ./thedpool -
CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(main) set(CMAKE_CXX_STANDARD 11) add_executable(thedpool ./src/main.cpp ./src/thrdpool.cpp) -
thrdpool.h
#ifndef SRC_KCEP_THRDPOOL #define SRC_KCEP_THRDPOOL #include<queue> #include<vector> #include<pthread.h> #include <atomic> #include <mutex> #include <unistd.h> class func; using thrdpool_t_func_ptr = void* (*)(void*); using func_t = func; class func { public: func() { key = func_key_auto++; func_state = 0; } public: //任务函数 thrdpool_t_func_ptr exec_func; //函数参数 void* arg; //0为未执行,1 为正在执行,2 为正常退出 short func_state; //函数返回值 void* returnValue; //函数唯一识别码 long long key; //函数识别码创建 static std::atomic<long long> func_key_auto; }; class ThrdPool { public: //创建一个线程池并指定线程池线程数量 ThrdPool(int n); //析构 ~ThrdPool(); //删除线程池,内置线程和任务将会被强制清除 void thrdpoolStopAll(); //向任务队列压入一个任务函数,此任务队列大小可比线程数量高 func_t* thrdpoolPost(thrdpool_t_func_ptr func_p, void* _arg); //等待一个线程池所有任务执行结束 void thrdpool_join(); //获取函数返回值 void* getFuncValue(func_t* p); //线程池正在执行的线程数量 int getExecLength(); //线程池当前队列任务量 int getQueueLength(); private: //线程池空转函数,等待任务队列有任务提出执行 static void* thrdExecFunc(void* data); //待执行队列 std::queue<func_t*> funcQueue; //执行完毕列表,可从此列表获取返回值 std::vector<func_t*> funcList; //正在执行的数量 int exec_length; //最大线程数量 int size; //线程句柄 pthread_t* thrd_arr; //锁 std::mutex k; //正在执行任务的线程数量 std::atomic<int> exec_func_num; }; #endif -
thrdpool.cpp
#include "thrdpool.h" #include<iostream> std::atomic<long long> func::func_key_auto = { 0 }; ThrdPool::ThrdPool(int n) { if (n < 1)n = 1; size = n; exec_length = 0; exec_func_num = 0; thrd_arr = new pthread_t[n]; //创建空转函数实例 for (int i = 0;i < n;i++) { pthread_create(&thrd_arr[i], nullptr, thrdExecFunc, this); } } ThrdPool::~ThrdPool() { while (!funcQueue.empty()) { delete funcQueue.front(); funcQueue.pop(); } for (auto i : funcList) { delete i; i = nullptr; } delete[] thrd_arr; thrd_arr = nullptr; } void ThrdPool::thrdpoolStopAll() { for (int i = 0;i < size;i++) { //强行注销线程 pthread_cancel(thrd_arr[i]); //将已被注销的线程设置为无主线程 pthread_detach(thrd_arr[i]); } while (!funcQueue.empty()) { delete funcQueue.front(); funcQueue.pop(); } for (auto i : funcList) { delete i; i = nullptr; } delete[] thrd_arr; thrd_arr = nullptr; } func_t* ThrdPool::thrdpoolPost(thrdpool_t_func_ptr func_p, void* _arg) { func_t* func_exec = new func_t; func_exec->exec_func = func_p; func_exec->arg = _arg; k.lock(); funcQueue.push(func_exec); k.unlock(); return func_exec; } void ThrdPool::thrdpool_join() { while (true) { k.lock(); if (exec_func_num == 0 && funcQueue.size() == 0) { k.unlock(); break; } else { k.unlock(); usleep(1000); } } } void* ThrdPool::getFuncValue(func_t* p) { if (p->func_state != 2) { std::cerr << "\033[1;31m" << "Error:" << "\033[0m" << "无对应返回值,线程可能未执行或者未结束" << std::endl; return nullptr; } return p->returnValue; } int ThrdPool::getExecLength() { return exec_func_num; } int ThrdPool::getQueueLength() { return funcQueue.size(); } void* ThrdPool::thrdExecFunc(void* data) { ThrdPool* p = (ThrdPool*)data; //data 为 this 指针 while (true) { p->k.lock(); //加锁 if (!p->funcQueue.empty()) { //当队列不为空时 func_t* func_exec = p->funcQueue.front(); //从队列顶部取出一个函数指针(任务) p->funcQueue.pop(); //弹出队列的函数指针 func_exec->func_state = 1; //将任务状态改为执行中 p->exec_func_num++; //正在执行的任务量+1 exec_func_num是原子变量就不加锁了 p->k.unlock(); //解锁 func_exec->returnValue = func_exec->exec_func(func_exec->arg); //将函数返回值加入 func_exec p->k.lock(); //加锁 func_exec->func_state = 2; //将任务状态改为执行结束 p->funcList.push_back(func_exec); //将任务状态加入列表中,可以根据此状态获取返回值 p->k.unlock(); //解锁 p->exec_func_num--; //正在执行任务量-1 } else { //当队列为空时 p->k.unlock(); //解锁 usleep(5000); //等待 5000 微秒防止 CPU 空转 } } return nullptr; } -
main.cpp
#include"thrdpool.h" #include<iostream> using namespace std; void* func(void* p){ cout<<*((int*)(p))<<endl; int* s=new int; *s=234; delete p; return s; } int main(){ ThrdPool p(2); auto i=p.thrdpoolPost(func,new int(999)); p.thrdpool_join(); cout<<*((int*)p.getFuncValue(i))<<endl; delete p.getFuncValue(i); usleep(5000); }

3135

被折叠的 条评论
为什么被折叠?



