【C++】基于C++11的线程池:threadpool

1、参考

作者博客:https://www.cnblogs.com/lzpong/p/6397997.html
源码:https://github.com/lzpong/threadpool

2、源码

原理:利用生产者-消费者模型,管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。

#pragma once
#ifndef THREAD_POOL_H
#define THREAD_POOL_H#include <vector>
#include <queue>
#include <atomic>
#include <future>
#include <stdexcept>namespace std
{
//线程池最大容量,应尽量设小一点
#define  THREADPOOL_MAX_NUM 16
//线程池是否可以自动增长(如果需要,且不超过 THREADPOOL_MAX_NUM)
//#define  THREADPOOL_AUTO_GROW//线程池,可以提交变参函数或拉姆达表达式的匿名函数执行,可以获取执行返回值
//不直接支持类成员函数, 支持类静态成员函数或全局函数,Opteron()函数等
class threadpool
{unsigned short _initSize;       //初始化线程数量using Task = function<void()>; //定义类型vector<thread> _pool;          //线程池queue<Task> _tasks;            //任务队列mutex _lock;                   //任务队列同步锁
#ifdef THREADPOOL_AUTO_GROWmutex _lockGrow;               //线程池增长同步锁
#endif // !THREADPOOL_AUTO_GROWcondition_variable _task_cv;   //条件阻塞atomic<bool> _run{ true };     //线程池是否执行atomic<int>  _idlThrNum{ 0 };  //空闲线程数量public:inline threadpool(unsigned short size = 4) { _initSize = size; addThread(size); }inline ~threadpool(){_run=false;_task_cv.notify_all(); // 唤醒所有线程执行for (thread& thread : _pool) {//thread.detach(); // 让线程“自生自灭”if (thread.joinable())thread.join(); // 等待任务结束, 前提:线程一定会执行完}}public:// 提交一个任务// 调用.get()获取返回值会等待任务执行完,获取返回值// 有两种方法可以实现调用类成员,// 一种是使用   bind: .commit(std::bind(&Dog::sayHello, &dog));// 一种是用   mem_fn: .commit(std::mem_fn(&Dog::sayHello), this)template<class F, class... Args>auto commit(F&& f, Args&&... args) -> future<decltype(f(args...))>{if (!_run)    // stoped ??throw runtime_error("commit on ThreadPool is stopped.");using RetType = decltype(f(args...)); // typename std::result_of<F(Args...)>::type, 函数 f 的返回值类型auto task = make_shared<packaged_task<RetType()>>(bind(forward<F>(f), forward<Args>(args)...)); // 把函数入口及参数,打包(绑定)future<RetType> future = task->get_future();{    // 添加任务到队列lock_guard<mutex> lock{ _lock };//对当前块的语句加锁  lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock()_tasks.emplace([task]() { // push(Task{...}) 放到队列后面(*task)();});}
#ifdef THREADPOOL_AUTO_GROWif (_idlThrNum < 1 && _pool.size() < THREADPOOL_MAX_NUM)addThread(1);
#endif // !THREADPOOL_AUTO_GROW_task_cv.notify_one(); // 唤醒一个线程执行return future;}// 提交一个无参任务, 且无返回值template <class F>void commit2(F&& task){if (!_run) return;{lock_guard<mutex> lock{ _lock };_tasks.emplace(std::forward<F>(task));}
#ifdef THREADPOOL_AUTO_GROWif (_idlThrNum < 1 && _pool.size() < THREADPOOL_MAX_NUM)addThread(1);
#endif // !THREADPOOL_AUTO_GROW_task_cv.notify_one();}//空闲线程数量int idlCount() { return _idlThrNum; }//线程数量int thrCount() { return _pool.size(); }#ifndef THREADPOOL_AUTO_GROW
private:
#endif // !THREADPOOL_AUTO_GROW//添加指定数量的线程void addThread(unsigned short size){
#ifdef THREADPOOL_AUTO_GROWif (!_run)    // stoped ??throw runtime_error("Grow on ThreadPool is stopped.");unique_lock<mutex> lockGrow{ _lockGrow }; //自动增长锁
#endif // !THREADPOOL_AUTO_GROWfor (; _pool.size() < THREADPOOL_MAX_NUM && size > 0; --size){   //增加线程数量,但不超过 预定义数量 THREADPOOL_MAX_NUM_pool.emplace_back( [this]{ //工作线程函数while (true) //防止 _run==false 时立即结束,此时任务队列可能不为空{Task task; // 获取一个待执行的 task{// unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()unique_lock<mutex> lock{ _lock };_task_cv.wait(lock, [this] { // wait 直到有 task, 或需要停止return !_run || !_tasks.empty();});if (!_run && _tasks.empty())return;_idlThrNum--;task = move(_tasks.front()); // 按先进先出从队列取一个 task_tasks.pop();}task();//执行任务
#ifdef THREADPOOL_AUTO_GROWif (_idlThrNum>0 && _pool.size() > _initSize) //支持自动释放空闲线程,避免峰值过后大量空闲线程return;
#endif // !THREADPOOL_AUTO_GROW{unique_lock<mutex> lock{ _lock };_idlThrNum++;}}});{unique_lock<mutex> lock{ _lock };_idlThrNum++;}}}
};
}
#endif  //https://github.com/lzpong/

3、涉及的C++11的知识

1)using Task = function<void()> 是类型别名,简化了 typedef 的用法。function<void()> 可以认为是一个函数类型,接受任意原型是 void() 的函数,或是函数对象,或是匿名函数。void() 意思是不带参数,没有返回值。

2)pool.emplace_back([this]{…}) 和 pool.push_back([this]{…}) 功能一样,只不过前者性能会更好;

3)pool.emplace_back([this]{…}) 是构造了一个线程对象,执行函数是拉姆达匿名函数 ;

4)所有对象的初始化方式均采用了 {},而不再使用 () 方式,因为风格不够一致且容易出错;

5)匿名函数: [this]{…} 不多说。[] 是捕捉器,this 是引用域外的变量 this指针, 内部使用死循环, 由cv_task.wait(lock,[this]{…}) 来阻塞线程;

6)delctype(expr) 用来推断 expr 的类型,和 auto 是类似的,相当于类型占位符,占据一个类型的位置;auto f(A a, B b) -> decltype(a+b) 是一种用法,不能写作 decltype(a+b) f(A a, B b),为啥?! c++ 就是这么规定的!

7)commit 方法是不是略奇葩!可以带任意多的参数,第一个参数是 f,后面依次是函数 f 的参数(注意:参数要传struct/class的话,建议用pointer,小心变量的作用域)! 可变参数模板是 c++11 的一大亮点,够亮!至于为什么是 Arg… 和 arg… ,因为规定就是这么用的!

8)commit 直接使用智能调用stdcall函数,但有两种方法可以实现调用类成员,一种是使用 bind: .commit(std::bind(&Dog::sayHello, &dog)); 一种是用 mem_fn: .commit(std::mem_fn(&Dog::sayHello), &dog);

9)make_shared 用来构造 shared_ptr 智能指针。用法大体是 shared_ptr p = make_shared(4) 然后 *p == 4 。智能指针的好处就是, 自动 delete !

10)bind 函数,接受函数 f 和部分参数,返回currying后的匿名函数,譬如 bind(add, 4) 可以实现类似 add4 的函数!

11)forward() 函数,类似于 move() 函数,后者是将参数右值化,前者是… 肿么说呢?大概意思就是:不改变最初传入的类型的引用类型(左值还是左值,右值还是右值);

12)packaged_task 就是任务函数的封装类,通过 get_future 获取 future , 然后通过 future 可以获取函数的返回值(future.get());packaged_task 本身可以像函数一样调用 () ;

13)queue 是队列类, front() 获取头部元素, pop() 移除头部元素;back() 获取尾部元素,push() 尾部添加元素;

14)lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock(),是 c++ RAII 的 idea;

15)condition_variable cv; 条件变量, 需要配合 unique_lock 使用;unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()。 cv.wait() 之前需要持有 mutex,wait 本身会 unlock() mutex,如果条件满足则会重新持有 mutex。

16)最后线程池析构的时候,join() 可以等待任务都执行完在结束,很安全!

4、使用demo

#include "threadpool.h"
#include <iostream>void fun1(int slp)
{printf("  hello, fun1 !  %d\n" ,std::this_thread::get_id());if (slp>0) {printf(" ======= fun1 sleep %d  =========  %d\n",slp, std::this_thread::get_id());std::this_thread::sleep_for(std::chrono::milliseconds(slp));}
}struct gfun {int operator()(int n) {printf("%d  hello, gfun !  %d\n" ,n, std::this_thread::get_id() );return 42;}
};class A {
public:static int Afun(int n = 0) {   //函数必须是 static 的才能直接使用线程池std::cout << n << "  hello, Afun !  " << std::this_thread::get_id() << std::endl;return n;}static std::string Bfun(int n, std::string str, char c) {std::cout << n << "  hello, Bfun !  "<< str.c_str() <<"  " << (int)c <<"  " << std::this_thread::get_id() << std::endl;return str;}
};int main()try {std::threadpool executor{ 50 };A a;std::future<void> ff = executor.commit(fun1,0);std::future<int> fg = executor.commit(gfun{},0);std::future<int> gg = executor.commit(a.Afun, 9999); //IDE提示错误,但可以编译运行std::future<std::string> gh = executor.commit(A::Bfun, 9998,"mult args", 123);std::future<std::string> fh = executor.commit([]()->std::string { std::cout << "hello, fh !  " << std::this_thread::get_id() << std::endl; return "hello,fh ret !"; });std::cout << " =======  sleep ========= " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::microseconds(900));for (int i = 0; i < 50; i++) {executor.commit(fun1,i*100 );}std::cout << " =======  commit all ========= " << std::this_thread::get_id()<< " idlsize="<<executor.idlCount() << std::endl;std::cout << " =======  sleep ========= " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::seconds(3));ff.get(); //调用.get()获取返回值会等待线程执行完,获取返回值std::cout << fg.get() << "  " << fh.get().c_str()<< "  " << std::this_thread::get_id() << std::endl;std::cout << " =======  sleep ========= " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::seconds(3));std::cout << " =======  fun1,55 ========= " << std::this_thread::get_id() << std::endl;executor.commit(fun1,55).get();    //调用.get()获取返回值会等待线程执行完std::cout << "end... " << std::this_thread::get_id() << std::endl;std::threadpool pool(4);std::vector< std::future<int> > results;for (int i = 0; i < 8; ++i) {results.emplace_back(pool.commit([i] {std::cout << "hello " << i << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "world " << i << std::endl;return i*i;}));}std::cout << " =======  commit all2 ========= " << std::this_thread::get_id() << std::endl;for (auto && result : results)std::cout << result.get() << ' ';std::cout << std::endl;return 0;}
catch (std::exception& e) {std::cout << "some unhappy happened...  " << std::this_thread::get_id() << e.what() << std::endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/590478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是JavaScript

文章目录 一、❄️什么是JavaScript&#xff1f;二、❄️JavaScript的特点三、❄️JavaScript的组成&#x1f9eb;1、核心&#xff08;ECMAScript&#xff09;&#x1f9ff;2、文档对象模型&#xff08;DOM&#xff09;&#x1f94f;3、浏览器对象模型&#xff08;BOM&#xff…

深入理解MySQL索引底层数据结构

听课问题(听完课自己查资料) 什么是二叉树 二叉树是怎么存储数据的一个链表是一个集合的数据结构 List是怎么便利找到指定下标元素为什么会快&#xff1f;什么是红黑树 红黑树是怎么存储数据的什么是B TREE 是怎么存储数据的什么是BTREE 是怎么存储数据的 疑惑答案 a. 二叉树…

SQL性能优化-索引

1.性能下降sql慢执行时间长等待时间长常见原因 1&#xff09;索引失效 索引分为单索、复合索引。 四种创建索引方式 create index index_name on user (name); create index index_name_2 on user(id,name,email); 2&#xff09;查询语句较烂 3&#xff09;关联查询太多join&a…

【力扣100】【好题】200.岛屿数量

添加链接描述 解法一&#xff1a;dfs class Solution:def numIslands(self, grid: List[List[str]]) -> int:# 思路是dfs&#xff0c;使用一个指针遍历元素&#xff0c;如果找到1&#xff0c;就递归把跟这个1连着的1都变成0&#xff0c;用result记录结果if not grid or le…

如何学习TS?

文章目录 一. 8种内置基础类型.ts二. void、never、any、unknown类型void类型never类型any类型unknown类型总结&#xff1a;void和any在项目中是比较常见的&#xff0c;never和unknown不常用。 三. 数组和函数类型定义.ts四. 元组与交叉类型使用元组数组一般有同类型的值组成&a…

AutoSAR(基础入门篇)4.9-Autoar_BSW小结

Autoar_BSW小结 Autoar_BSW小结 一、Autoar_BSW小结 1、BSW组件图 2、BSW的功能概述 3、BSW在工程里的应用实际工程

ubuntu 20.04 自由切换 python 的版本

问题描述 当前 ubuntu 20.04 默认安装了多个 python 的版本&#xff0c;执行 python 时&#xff0c;默认版本是 Python 2.7.18 zhangszzhangsz:~$ python Python 2.7.18 (default, Jul 1 2022, 12:27:04) [GCC 9.4.0] on linux2 Type "help", "copyright&quo…

url编码未转义导致参数丢失

原来的请求&#xff1a; "&url${ctx}/loanform/risk/loanItemRiskItemReport/main.ht?baseProductType${baseProductType}""&itemReportId"itemReportId修改后&#xff1a; 原因&#xff1a;请求地址拼接时&#xff0c;会判断name为url的属性是…

多模态大模型的前世今生

1 引言 前段时间 ChatGPT 进行了一轮重大更新&#xff1a;多模态上线&#xff0c;能说话&#xff0c;会看图&#xff01;微软发了一篇长达 166 页的 GPT-4V 测评论文&#xff0c;一时间又带起了一阵多模态的热议&#xff0c;随后像是 LLaVA-1.5、CogVLM、MiniGPT-5 等研究工作…

什么是IDE?新手用哪个IDE比较好?

什么是 IDE&#xff1f; IDE&#xff08;Integrated Development Environment&#xff0c;集成开发环境&#xff09;是提供给程序员用来编写代码的软件应用程序。一个 IDE 通常包含以下组件&#xff1a; 代码编辑器&#xff1a;支持编写和编辑源代码的文本编辑器。 编译器或解…

Impala4.x源码阅读笔记(三)——Impala如何管理Iceberg表元数据

前言 本文为笔者个人阅读Apache Impala源码时的笔记&#xff0c;仅代表我个人对代码的理解&#xff0c;个人水平有限&#xff0c;文章可能存在理解错误、遗漏或者过时之处。如果有任何错误或者有更好的见解&#xff0c;欢迎指正。 上一篇文章Impala4.x源码阅读笔记&#xff0…

simulink代码生成(五)——ePWM模块初级应用

前面分别讲到了SCI及ADC的配置及使用&#xff0c;现在梳理一下ePWM的配置和使用&#xff1b; 先打一些基础的DSP28335的基础知识&#xff1b; F28335 关于ePWM中断与SOC采样信号的一些思考_socasel-CSDN博客 F28335 ePWM模块简介——TMS320F28335学习笔记&#xff08;四&…

【虹科分享】利用ProfiShark 构建便携式网络取证工具包

文章速览&#xff1a; 为什么要使用便携式网络取证工具&#xff1f;构建便携式网络取证套件法证分析ProfiShark 1G作为便携式分路器的优点 网络安全领域日益重视便携式取证工具的灵活应用。本文介绍了如何构建一个以ProfiShark 1G为核心的便携式网络取证工具包&#xff0c;以提…

YHZ011 Python 显式类型转换

资源编号&#xff1a;YHZ011 配套视频&#xff1a;https://www.bilibili.com/video/BV1zy4y1Z7nk?p12 &#x1f981; 显式类型转换 在显式类型转换中&#xff0c;用户将对象的数据类型转换为所需的数据类型。 我们使用 int()、float()、str() 等预定义函数来执行显式类型转换…

软件开发必知必会的计算机基础

1.计算机基本介绍 1.1 什么是计算机 计算机(Computer)俗称为电脑&#xff0c;计算机是一种高速计算的电子机器&#xff0c;计算机可以进行数值运算&#xff0c;逻辑判断&#xff0c;接收或者是存储信息数据(文本、图片、音频、视频)&#xff0c;按照存储在其内部的程序对海量的…

V8 环境搭建

前言 早就想入门V8了&#xff0c;但是之前环境配置搞了好几次都没成功&#xff0c;所以就放弃了。之前一直想着给虚拟机搭全局VPN &#xff0c;但是其实根本没那么麻烦。 准备 Ubuntu 18.04&#xff1a;据说该版本是最匹配V8的&#xff0c;当然也有说最好用 20.04 的&#x…

Vite+Vue3使用MockJS

在使用Vue3开发的时候&#xff0c;有时候没有后端或者后端接口还没有准备好&#xff0c;那就需要使用Mock模拟数据便于前端开发。 现在就记录一下ViteVue3的环境下如果使用MockJS。 版本 vue 3.3.11mockjs 1.1.0axios 1.6.3 Mockjs配置使用 使用pnpm命令安装Mockjs pnpm …

蓝桥杯python比赛历届真题99道经典练习题 (41-50)

【程序41】 题目:学习static定义静态变量的用法    1.程序分析: 2.程序源代码: # python没有这个功能了,只能这样了:) def varfunc():var = 0print var = %d % varvar += 1 if __name__ == __main__:for i in range(3):varfunc()# attribut of class # 作为类的一个属…

5.微服务代码模型

1.微服务代码模型 代码分层 在微服务代码模型里&#xff0c;我们分别定义了用户接口层、并分别为它们建立了interfaces、application、domain和infrastructure四个一级代码目录&#xff1b; interfaces(用户接口层): 它主要存放用户接口层与前端应用交互、数据转换和交互相关…

Ultra ISO 虚拟光驱修改光盘盘符

windows xp 环境 ultra iso 虚拟光驱修改光盘盘符 method 1. 在ultra iso 中 [选项]->[配置]->[虚拟光驱]&#xff0c;在新盘符里选指定盘符 ->[修改] method 2. 打开命令行&#xff0c;进入安装目录&#xff0c;如 "C:\Program Files\UltraISO\drivers"&…