使用 C++11 编写可复用多线程任务池
类的功能
Task (任务基类)
该类主要实现一个任务类
virtual int doWork() = 0;TaskQueue (任务队列)
该类主要针对任务的存储、删除、撤回等状态做管理ThreadPool (线程池)
整个线程池的核心业务处理类
代码
- Task.h
//任务的基类
#pragma once
#include <time.h>
#include <atomic>
//任务的基类
class Task
{
public:
//构造、析构函数
Task():_id(_nRequestID++),_isCancelRequired(false),_createTime(clock()){}
~Task(){};
// 任务类虚接口,继承这个类的必须要实现这个接口
virtual int doWork(void) = 0;
// 任务已取消回调
virtual int onCanceled(void)
{
return 1;
}
// 任务已完成
virtual int onCompleted(int)
{
return 1;
}
// 任务是否超时
virtual bool isTimeout(const clock_t& now)
{
return ((now - _createTime) > 5000);
}
// 获取任务ID
size_t getID(void)
{
return _id;
}
//获取任务取消状态
bool isCancelRequired(void)
{
return _isCancelRequired;
}
//设置任务取消状态
void setCancelRequired(void)
{
_isCancelRequired = true;
}
protected:
size_t _id; //任务的唯一标识
clock_t _createTime; //任务创建时间,非Unix时间戳
private:
static std::atomic<size_t> _nRequestID;
std::atomic<bool> _isCancelRequired; //任务取消状态
};
//selectany可以让我们在.h文件中初始化一个全局变量而不是只能放在.cpp中。
//这样的代码来初始化这个全局变量。既是该.h被多次include,链接器也会为我们剔除多重定义的错误。
__declspec(selectany) std::atomic<size_t> Task::_nRequestID = 100000;
- TaskQueue.h
#pragma once
#include <deque>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include <memory>
#include <thread>
//任务队列
template<typename T>
class TaskQueue
{
public:
//向队列的末尾插入任务,task是任务类
void put_back(std::shared_ptr<T>& task)
{
std::unique_lock<std::mutex> lock(_mutexQueue);
_queue.push_back(task);
_conditPut.notify_one();
}
//向队列的头部插入任务
void put_front(std::shared_ptr<T>& task)
{
std::unique_lock<std::mutex> lock(_mutexQueue);
_queue.push_front(task);
_conditPut.notify_one();
}
//获取队首(并将任务加到运行任务列表中),返回tase是任务类
std::shared_ptr<T> get(void) {
std::unique_lock<std::mutex> lock(_mutexQueue);
if (_queue.empty())
return nullptr;
//lock_guard取代了mutex的lock()和unlock();
std::lock_guard<std::mutex> lock_doing_task(_mutexDoingTask);
std::shared_ptr<T>& task = _queue.front();
_mapDoingTask.insert(std::make_pair(task->getID(), task));
_queue.pop_front();
return task;
}
//获取双向链表queue的大小
size_t size(void)
{
std::unique_lock<std::mutex> lock(_mutexQueue);
return _queue.size();
}
//释放队列
void release(void)
{
deleteAllTasks();
_conditPut.notify_all();
}
//删除任务(从就绪队列删除,如果就绪队列没有,则看执行队列有没有,有的话置下取消状态位)
int deleteTask(size_t nID)
{
std::unique_lock<std::mutex> lock(_mutexQueue, std::defer_lock);
lock.lock();
auto it = _queue.begin();
for (; it != _queue.end(); ++it)
{
if ((*it)->getID() == nID)
{
_queue.erase(it);
lock.unlock();
return 0;
}
}
//下面的逻辑可能会造成死锁,这里要及时释放
lock.unlock();
// 试图取消正在执行的任务
{
std::lock_guard<std::mutex> lock_doing_task(_mutexDoingTask);
auto it_map = _mapDoingTask.find(nID);
if (it_map != _mapDoingTask.end())
it_map->second->setCancelRequired();
}
//任务执行完后再返回
while (_mapDoingTask.count(nID))
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
//删除所有任务
int deleteAllTasks(void)
{
std::unique_lock<std::mutex> lock(_mutexQueue, std::defer_lock);
lock.lock();
if (!_queue.empty())
_queue.clear();//清空
{
std::lock_guard<std::mutex> lock_doing_task(_mutexDoingTask);
if (!_mapDoingTask.empty())
{
auto it_map = _mapDoingTask.begin();
for (; it_map != _mapDoingTask.end(); ++it_map)
it_map->second->setCancelRequired();
}
}
lock.unlock();
//任务执行完后再返回
while (!_mapDoingTask.empty())
std::this_thread::sleep_for(std::chrono::milliseconds(50));
return 0;
}
//任务完成回调(从运行列表中删除指定任务)
int onTaskFinished(size_t nID)
{
std::lock_guard<std::mutex> lock_doing_task(_mutexDoingTask);
auto it_map = _mapDoingTask.find(nID);
if (it_map != _mapDoingTask.end())
_mapDoingTask.erase(it_map);
return 0;
}
//判断任务是否执行完毕
std::shared_ptr<T> isTaskProcessed(size_t nId)
{
std::lock_guard<std::mutex> lock_queue(_mutexQueue);
auto it = _queue.begin();
for (; it != _queue.end(); ++it) {
if ((*it)->getID() == nId)
return *it;
}
std::lock_guard<std::mutex> lock_doing_task(_mutexDoingTask);
auto it_map = _mapDoingTask.find(nId);
if (it_map != _mapDoingTask.end())
return it_map->second;
return nullptr;
}
//等待有任务到达(带超时:超时自动唤醒)
bool wait(std::chrono::milliseconds millsec)
{
std::unique_lock<std::mutex> lock(_mutexConditPut);
_conditPut.wait_for(lock, millsec);
return true;
}
private:
//就绪的任务
std::mutex _mutexQueue;
std::deque<std::shared_ptr<T>> _queue;
//条件变量
std::mutex _mutexConditPut;
std::condition_variable _conditPut;
//运行的任务
std::mutex _mutexDoingTask;
std::unordered_map<size_t, std::shared_ptr<T> > _mapDoingTask;
};
- ThreadPool.h
#pragma once
#include <atomic>
#include <memory>
#include <mutex>
#include <iostream>
#include <thread>
#include "Task.h"
#include "TaskQueue.h"
class ThreadPool
{
public:
// 线程池配置参数
typedef struct tagThreadPoolConfig {
int nMaxThreadsNum; // 最大线程数量
int nMinThreadsNum; // 最小线程数量
double dbTaskAddThreadRate; // 增 最大线程任务比 (任务数量与线程数量,什么比例的时候才加)
double dbTaskSubThreadRate; // 减 最小线程任务比 (任务数量与线程数量,什么比例的时候才减)
} ThreadPoolConfig;
public:
//构造函数
ThreadPool(void):_taskQueue(new TaskQueue<Task>()), _atcCurTotalThrNum(0), _atcWorking(true){}
//析构函数
~ThreadPool(void)
{
release();
}
//初始化资源
int init(const ThreadPoolConfig& threadPoolConfig) {
// 错误的设置
if (threadPoolConfig.dbTaskAddThreadRate < threadPoolConfig.dbTaskSubThreadRate)
return 87;
_threadPoolConfig.nMaxThreadsNum = threadPoolConfig.nMaxThreadsNum;
_threadPoolConfig.nMinThreadsNum = threadPoolConfig.nMinThreadsNum;
_threadPoolConfig.dbTaskAddThreadRate = threadPoolConfig.dbTaskAddThreadRate;
_threadPoolConfig.dbTaskSubThreadRate = threadPoolConfig.dbTaskSubThreadRate;
int ret = 0;
// 创建线程池
if (_threadPoolConfig.nMinThreadsNum > 0)
ret = addProThreads(_threadPoolConfig.nMinThreadsNum);
return ret;
}
// 添加任务
int addTask(std::shared_ptr<Task> taskptr, bool priority=false)
{
const double& rate = getThreadTaskRate();
int ret = 0;
if (priority)
{
if (rate > 1000)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
_taskQueue->put_front(taskptr);
}
else
{
// 检测任务数量
if (rate > 100) {
taskptr->onCanceled();
return 298;
}
// 将任务推入队列
_taskQueue->put_back(taskptr);
}
// 检查是否要扩展线程
if (_atcCurTotalThrNum < _threadPoolConfig.nMaxThreadsNum
&& rate > _threadPoolConfig.dbTaskAddThreadRate)
ret = addProThreads(1);
return ret;
}
// 删除任务(从就绪队列删除,如果就绪队列没有,则看执行队列有没有,有的话置下取消状态位)
int deleteTask(size_t nID)
{
return _taskQueue->deleteTask(nID);
}
// 删除所有任务
int deleteAllTasks(void)
{
return _taskQueue->deleteAllTasks();
}
std::shared_ptr<Task> isTaskProcessed(size_t nId)
{
return _taskQueue->isTaskProcessed(nId);
}
// 释放资源(释放线程池、释放任务队列)
bool release(void)
{
// 1、停止线程池。
// 2、清楚就绪队列。
// 3、等待执行队列为0
releaseThreadPool();
_taskQueue->release();
int i = 0;
while (_atcCurTotalThrNum != 0)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 异常等待
if (i++ == 10)
exit(23);
}
_atcCurTotalThrNum = 0;
return true;
}
// 获取当前线程任务比
double getThreadTaskRate(void)
{
if (_atcCurTotalThrNum != 0)
return _taskQueue->size() * 1.0 / _atcCurTotalThrNum;
return 0;
}
// 当前线程是否需要结束
bool shouldEnd(void)
{
bool bFlag = false;
double dbThreadTaskRate = getThreadTaskRate();
// 检查线程与任务比率
if (!_atcWorking || _atcCurTotalThrNum > _threadPoolConfig.nMinThreadsNum
&& dbThreadTaskRate < _threadPoolConfig.dbTaskSubThreadRate)
bFlag = true;
return bFlag;
}
// 释放线程池
bool releaseThreadPool(void)
{
_threadPoolConfig.nMinThreadsNum = 0;
_threadPoolConfig.dbTaskSubThreadRate = 0;
_atcWorking = false;
return true;
}
// 添加指定数量的处理线程
int addProThreads(int nThreadsNum)
{
try {
for (; nThreadsNum > 0; --nThreadsNum)
std::thread(&ThreadPool::taskProcessThread, this).detach();
}
catch (...){
return 155;
}
return 0;
}
// 任务处理线程函数
void taskProcessThread(void)
{
int nTaskProcRet = 0;
// 线程增加
_atcCurTotalThrNum.fetch_add(1);
std::chrono::milliseconds mills_sleep(500);
std::shared_ptr<Task> pTask;
while (_atcWorking)
{
// 从任务队列中获取任务
pTask = _taskQueue->get(); //get会将任务添加到运行任务的map中去
if (pTask == nullptr)
{
if (shouldEnd())
break;
// 进入睡眠池
_taskQueue->wait(mills_sleep);
continue;
}
// 检测任务取消状态
if (pTask->isCancelRequired())
pTask->onCanceled();
else
// 处理任务
pTask->onCompleted(pTask->doWork());
// 从运行任务队列中移除任务
_taskQueue->onTaskFinished(pTask->getID());
// 判断线程是否需要结束
if (shouldEnd())
break;
}
// 线程个数减一
_atcCurTotalThrNum.fetch_sub(1);
}
private:
std::shared_ptr<TaskQueue<Task> > _taskQueue; //任务队列
ThreadPoolConfig _threadPoolConfig; //线程池配置
std::atomic<bool> _atcWorking; //线程池是否被要求结束
std::atomic<int> _atcCurTotalThrNum; //当前线程个数
};
- FunTask.h
#pragma once
#include <functional>
#include "Task.h"
class FuncTask:public Task
{
public:
FuncTask(std::function<int(void)> f) : _pf(f) {}
FuncTask(void) : _pf(nullptr){}
virtual ~FuncTask(){}
template <typename F,typename... Args>
void asynBind(F(*f)(Args...), Args... args)
{
_pf = std::bind(f, args...);
}
virtual int doWork()
{
if (_pf == nullptr)
return 86;
return _pf();
}
private:
typedef std::function<int(void)> pvFunc;
pvFunc _pf;
};
- main.cpp
#pragma once
#include <time.h>
#include <iostream>
#include <memory>
#include <string>
#include "ThreadPool.h"
#include "FuncTask.h"
using namespace std;
int vFunction(void)
{
std::cout << __FUNCTION__ << std::endl;
return 0;
}
int counter(int a,int b)
{
std::cout << a << ":" << b << std::endl;
return 0;
}
int main()
{
ThreadPool::ThreadPoolConfig threadPoolConfig;
threadPoolConfig.nMaxThreadsNum = 100;
threadPoolConfig.nMinThreadsNum = 5;
threadPoolConfig.dbTaskAddThreadRate = 3;
threadPoolConfig.dbTaskSubThreadRate = 0.5;
clock_t start = clock();
{
std::shared_ptr<ThreadPool> threadPool(new ThreadPool);
threadPool->init(threadPoolConfig);
int i = 1;
while (true)
{
/*std::shared_ptr<FuncTask> request(new FuncTask(vFunction));
threadPool->addTask(request);*/
std::shared_ptr<FuncTask> request(new FuncTask);
request->asynBind(counter, i++, 1);
threadPool->addTask(request);
if (request->getID() == 110000) {
break;
}
}
threadPool->release();
}
clock_t finish = clock();
std::cout << "duration:" << finish - start << "ms" << std::endl;
cout << "main:thread" << endl;
return 0;
}
参考
使用 C++11 编写可复用多线程任务池的更多相关文章
- Linux多线程实践(10) --使用 C++11 编写 Linux 多线程程序
在这个多核时代,如何充分利用每个 CPU 内核是一个绕不开的话题,从需要为成千上万的用户同时提供服务的服务端应用程序,到需要同时打开十几个页面,每个页面都有几十上百个链接的 web 浏览器应用程序,从 ...
- [转]使用 C++11 编写 Linux 多线程程序
前言 在这个多核时代,如何充分利用每个 CPU 内核是一个绕不开的话题,从需要为成千上万的用户同时提供服务的服务端应用程序,到需要同时打开十几个页面,每个页面都有几十上百个链接的 web 浏览器应用程 ...
- 跨平台轻量级redis、ssdb代理服务器(C++ 11编写)
dbproxy 是我业余采用C++11编写的跨平台代理服务器(并使用lua和自己的网络库),以扩展系统负载,同时使用多个后端数据库,后端数据库支持redis和ssdb. 需要由用户自己编写lua脚本控 ...
- 使用 C++11 编写类似 QT 的信号槽——下篇
要实现 Signal-Slot,Signal 类中应该拥有一个保存 std::function 的数组: template<class FuncType> class Signal { p ...
- 使用 C++11 编写类似 QT 的信号槽——上篇
了解 QT 的应该知道,QT 有一个信号槽 Singla-Slot 这样的东西.信号槽是 QT 的核心机制,用来替代函数指针,将不相关的对象绑定在一起,实现对象间的通信. 考虑为 Simple2D 添 ...
- C++11并发编程:多线程std::thread
一:概述 C++11引入了thread类,大大降低了多线程使用的复杂度,原先使用多线程只能用系统的API,无法解决跨平台问题,一套代码平台移植,对应多线程代码也必须要修改.现在在C++11中只需使用语 ...
- c++11 Thread库写多线程程序
一个简单的使用线程的Demo c++11提供了一个新的头文件<thread>提供了对线程函数的支持的声明(其他数据保护相关的声明放在其他的头文件中,暂时先从thread头文件入手吧),写一 ...
- Linux网络通信(TCP套接字编写,多进程多线程版本)
预备知识 源IP地址和目的IP地址 IP地址在上一篇博客中也介绍过,它是用来标识网络中不同主机的地址.两台主机进行通信时,发送方需要知道自己往哪一台主机发送,这就需要知道接受方主机的的IP地址,也就是 ...
- Linux下C编写基本的多线程socket服务器
不想多说什么,会搜这些东西的都是想看代码的吧. 一开始不熟悉多线程的时候还在想怎么来控制一个线程的结束,后来发现原来有pthread_exit()函数可以直接在线程函数内部调用结束这个线程. 开始还想 ...
随机推荐
- Binder 原理整理
linux进程间通信方式 1. 管道 管道的实质是一个内核缓冲区,管道的作用正如其名,需要通信的两个进程在管道的两端,进程利用管道传递信息.管道对于管道两端的进程而言,就是一个文件,但是这个文件比较特 ...
- mysql 不能加载表问题
记录一次 mysql 5.7 下,出现重启数据库后不能加载特定表的问题处理. 搜索了很多的类似的错误,大多都是说因为外键同名的索引丢失的情况.但在5.7这个版本下,会禁止更新外键关联的索引. 最后经过 ...
- 【56】目标检测之NMS非极大值抑制
非极大值抑制(Non-max suppression) 到目前为止你们学到的对象检测中的一个问题是,你的算法可能对同一个对象做出多次检测,所以算法不是对某个对象检测出一次,而是检测出多次.非极大值抑制 ...
- 使用node.js实现apache功能
1.先实现在url中输入文件路径能展示对应文件内容功能 const http = require('http') const fs = require('fs') const server = htt ...
- 清北学堂—2020.1提高储备营—Day 2 afternoon(线段树、树状数组)
qbxt Day 2 afternoon --2020.1.18 济南 主讲:李佳实 目录一览 1.线段树 2.二叉搜索树(略过) 3.树状数组 总知识点:基础数据结构(本人初学感觉好难) 一.线段树 ...
- 部署Nexus作为docker的私有仓库
目录 Docker搭建Nexus私有仓库... 1 一.安装部署... 1 1.安装... 2 2.访问网页端... 2 二.配置使用... 2 1.创建本地仓库... 2 2.docker配置... ...
- Vue图片验证码-自定义组件高级版
最近项目中要用到图片验证码,网上一查有很多,基本都是千篇一律的4位纯数字验证码.首先得感谢那位一代目兄台提供的模板,由于不能满足需求,所以对其进行了改造升级. 经改造的图片验证码能满足一下情形使用:① ...
- 使用faker 生成测试数据
测试数据生成 faker基础使用 from faker import Faker f=Faker(locale='zh_CN') print(f.name()) address 地址 person 人 ...
- Linux网络课程学习第二天
第二天学习日志: 今天的课程主要内容: 详细介绍了如何安装红帽RHEL7的系统,并对RPM,Yum,Systemd和bash进行了简单介绍.
- 【你不知道的javaScript 上卷 笔记7】javaScript中对象的[[Prototype]]机制
[[Prototype]]机制 [[Prototype]]是对象内部的隐试属性,指向一个内部的链接,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototyp ...