C++11实现的100行线程池(c++线程detach)
ahcoder 2025-01-23 14:58 42 浏览
linux服务器开发相关视频解析:
BAT面试必备:多线程、多进程、协程如何选择及线程池如何最高效
C++线程池一直都是各位程序员们造轮子的首选项目之一。今天,小编带大家一起来看看这个轻量的线程池,本线程池是header-only的,并且整个文件只有100行,其中C++的高级用法有很多,很值得我们学习,一起来看看吧。
线程池
C++带有线程操作,异步操作,就是没有线程池,至于线程池的概念,我先搜一下别人的解释:
一般而言,线程池有以下几个部分:
1. 完成主要任务的一个或多个线程。
2. 用于调度管理的管理线程。
3. 要求执行的任务队列。
我来讲讲人话:你的函数需要在多线程中运行,但是你又不能每来一个函数就开启一个线程,所以你就需要固定的N个线程来跑执行,但是有的线程还没有执行完,有的又在空闲,如何分配任务呢,你就需要封装一个线程池来完成这些操作,有了线程池这层封装,你就只需要告诉它开启几个线程,然后直接塞任务就行了,然后通过一定的机制获取执行结果。
分析源代码 头文件
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
vector,queue,momory 都没啥说的,thread线程相关,mutex 互斥量,解决资源抢占问题,condition_variable 条件量,用于唤醒线程和阻塞线程,future 从使用的角度出发,它是一个获取线程数据的函数。functional 函数子,可以理解为规范化的函数指针。stdexcept 就跟它的名字一样,标准异常。
class ThreadPool {
public:
ThreadPool(size_t);
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector< std::thread > workers;
// the task queue
std::queue< std::function<void()> > tasks;
// synchronization
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
线程池的声明,构造函数,一个enqueue模板函数 返回std::future<type>, 然后这个type又利用了运行时检测(还是编译时检测?)推断出来的,非常的amazing啊。成功的使用一行代码反复套娃,这高阶的用法就是大佬的水平吗,i了i了。
workers 是vector<std::thread> 俗称工作线程。
std::queue<std::function<void()>> tasks 俗称任务队列。
那么问题来了,这个任务队列的任务只能是void() 类型的吗?感觉没那么简单,还得接着看呐。
mutex,condition_variable 没啥讲的,stop 控制线程池停止的。
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for(size_t i = 0;i<threads;++i)
workers.emplace_back(
[this]
{
for(;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock,
[this]{ return this->stop || !this->tasks.empty(); });
if(this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
}
);
}
大佬写的注释就是这么朴实无华,说这个构造函数仅仅是把一定数量的线程塞进去,我是看了又看才悟出来这玩意是什么意思……虽然本质上的确是它说的只是把线程塞进去,但是这个线程也太绕了。
workers.emplace_back 参数是一个lambda表达式,不会阻塞,也就是说最外层的是一个异步函数,每个线程里面的事情才是重点。
labmda表达式中最外层是一个死循环,至于为什么是for(;;)而不是while(1) 这虽然不是重点,不过大佬的用法还是值得揣摩的,我估计是效率会更高?
task 申明后,紧跟着一个大括号,这个{}里面的部分,是一个同步操作,至于为什么用this->lock 而不是直接使用[&]来捕获参数,想来也是处于内存考虑。精打细算的风格像极了抠门的地主,i了i了。
【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)
紧接着一个wait(lock,condtion)的操作,像极了千层饼的套路。
第一层:这TM不是要锁死自己啊?这样不是构造都得卡死?
第二层:我们看到它emplace_back了一个线程,不会阻塞,但是等开锁,锁不就在它自己的线程里面嘛?那不得锁死了啊?
第三层:我们看到这个lock其实只是个包装,真正的锁是外层的mutex,所以从这里是不存在死锁的。但是你的wait的condition怎么可能不懂呢,必须要 stop 或者 !empty 才wait吗?
第四层:我们查资料发现后面的condition是返回false才会wait,也就是说要!stop && empty才会wait,就是说这个线程池是 运行态,并且没有任务才才会执行等待操作!否则就不等了,直接冲!
第五层:既然你判断了上面判断了stop和非空,为啥下面还要判断stop和空才退出呢?不显得冗余?
第六层:要确定它的确是被置为stop了,且队列执行空了,它才能够光荣退休。有没有问题呢,有,最后所有线程都阻塞了,你stop置为true它们也不知道啊……
我估计它的stop会有唤醒所有线程的操作,不过如果有的在执行,有的在等待,应该没办法都通知到位,但是在执行的在下一次判断的时候也能正常退出。
因为有了疑惑,我们就想看stop相关的操作,结果发现放在了析构函数里面……
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
{}里面上锁进行了stop为true的操作,至于为什么不用原子操作,我也不知道,但是仔细想了下大概是因为本来就有一把锁了,再用原子就不是内味儿了。然后它果然通知了所有,并且还把工作线程join了。也就是等它们结束。
结束了千层饼の解析之后,我们看看最重要的入队操作
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
typename std::result_of<F(Args...)>::type中的typename 应该是为消除歧义的,或者因为嵌套依赖名字的关系,做为一个坚决不写模板的普通程序员,这段代码太难了……-> type 我倒是知道怎么回事,就是指明它的返回类型的一种方式result_of<F(Args...)> 应该是指明了F是一个函数,签名为Args...这个变参,Args是啥它不关系,它关心的是返回值的参数类型 所以有个type。
至于为什么函数入口是一个右值引用那就超出我的理解范围了。难道说functional 必须要右值引用?那它的销毁谁来管呢?这个线程来管吗?这些坑我以后慢慢填。
前面我们说了tasks 只能接收void() 的函数类型,这里使用std::packaged_task<return_type()>完成对函数类型的推导,至于为什么不用 function<return_type()> ,因为这还不是最终放入tasks的对象,它要承接一个返回future<T>的工作,而package_task就是来打包返回future<T>的……
然后就是加锁入队+通知工作线程+返回future<T>的操作。本来是线程池最难理解的部分,反而显得平淡无奇了,因为前面那些花里胡哨的操作已经很好的打通了我们的理解能力。
相关推荐
- 如何在 Linux 中使用 which 命令?
-
在Linux的江湖中,每天都有成千上万的命令被执行。当你在终端输入python时,系统可能同时存在Python2.7和Python3.10;当你运行java命令时,可能意外调用了非预期的版本。这时...
- linux CentOS检查见后门程序的shell
-
#CentOS检查后门程序的Shell脚本以下是一个用于检查CentOS系统中潜在后门程序的BashShell脚本,包含多项安全检查:```bash#!/bin/bash#检查后门...
- Linux磁盘满了-服务器不打日志df&rm
-
大家好,我是「Bigder」、今天再说一个有意思的命令「df」,也是踩过坑的、怎么看磁盘占用情况?「df-h」命令用来显示磁盘占用率,截图里面可用是:17G、被使用11%,Use%达到100应用系统...
- Linux写脚本经常用到的测试命令(linux硬件测试脚本)
-
介绍一个Linux写脚本经常用到的测试命令testtest命令用于检查文件类型和比较值。Test用于条件执行。一、test常用于1.文件属性比较2.执行字符串比较3.基本的算术比较二、关系运算符...
- Linux History命令:如何显示命令执行的日期和时间
-
在Linux系统中,history命令是一个简单却强大的工具,它允许用户查看和重用之前执行过的命令。然而,默认情况下,history命令的输出仅显示命令的序号和内容,并不包含命令执行的日期和时间。这对...
- 判断Linux服务器架构是32位/64位
-
作为一个Unix系统的新手用户,我可以怎么判断我的Unix服务器安装的是32位或者64位的操作系统呢?你可以使用如下的命令来获取关于Unix内核和CPU架构的信息。getconf命令:显示机器硬件...
- linux服务器被黑快速排查(linux服务器被ddos攻击记录日志)
-
已更新:windows服务器被黑快速排查一般来说linux系统服务器被黑比较少,若怀疑服务器被黑了,可通过下述方法快速排查。下面是小梁的一些总结,可供大家参考。如有问题,欢迎大家在评论区留言交流。感谢...
- linux系统磁盘IO性能检测教程(linux磁盘io性能指标)
-
Linux系统中检测磁盘IO性能的教程在Linux系统中,监控和优化磁盘IO性能对系统的稳定性和效率至关重要,尤其是在高负载环境中。通过使用合适的工具,您可以检测系统的读写速度、IO等待时间以及每个进...
- Linux系统Shell脚本语言之循环及判断语句
-
摘要:在日常工作中或多或少都会接触到shell脚本,可以说会shell脚本是一位后端维护及开发的基本功。shell是一种编程语言,而学习一门编程语言语法,最基本的无外乎就是语言中的数据类型定义,for...
- 如何快速摸清LINUX系统的应用部署情况和正在运行的服务
-
作为运维人员或开发者,当接手一台新的Linux服务器时,第一要务就是摸清系统上已经安装部署了哪些应用和服务。本文将以CentOS7为例,详细介绍如何系统地排查已安装的应用和服务,包括它们的安装方...
- Linux服务器中毒?教你一步步精准判断和快速处置!
-
在当今网络安全威胁日益严峻的环境下,Linux虽然以其安全性著称,但也并非“刀枪不入”。许多黑客利用服务器漏洞、弱口令、过期软件等方式,渗透并植入恶意代码。一旦服务器被攻陷,可能导致数据泄露、资源...
- Linux-如何区分不同文件类型(linux怎么区分文件类型)
-
理解Linux一切皆文件的理念,掌握Linux下区分不同文件类型的多种方法(包括:通过颜色、用过文件类型字符、通过file命令及通过stat命令等方法)1.通过观察颜色可以最直观在命令行模式下区分不...
- 在Linux中输入一行命令后究竟发生了什么?
-
Linux,这个开源的操作系统巨人,以其强大的命令行界面而闻名。无论你是初学者还是经验丰富的系统管理员,理解在Linux终端输入一条命令并按下回车后发生的事情,都是掌握Linux核心的关键。从表面上看...
- 如何在 Linux 上设置和管理 VPN?
-
在Linux上设置和管理VPN是一个相对直接的过程,但需要一些基本的系统管理知识。这里,我们将探讨如何使用OpenVPN这个流行的VPN软件来实现这一目标。1.了解VPN的基本概念VPN,即虚拟私人...
- 从按下电源到登录界面!Linux启动全流程深度拆解,运维人必看
-
你要是用过Linux系统,肯定知道开机的时候,不是按个电源键,等着屏幕亮起来那么简单。背后的操作可复杂了,就像一场精心安排的大合唱,每个部分都在该出声的时候出声。今天,咱就来好好讲讲Linux...
- 一周热门
- 最近发表
- 标签列表
-
- linux 远程 (37)
- u盘 linux (32)
- linux 登录 (34)
- linux 路径 (33)
- linux 文件命令 (35)
- linux 是什么 (35)
- linux 界面 (34)
- 查看文件 linux (35)
- linux 语言 (33)
- linux代码 (32)
- linux 查看命令 (33)
- 关闭linux (34)
- root linux (33)
- 删除文件 linux (35)
- linux 主机 (34)
- linux与 (33)
- linux 函数 (35)
- linux .ssh (35)
- cpu linux (35)
- 查看linux 系统 (32)
- linux 防火墙 (33)
- linux 手机 (32)
- linux 镜像 (34)
- linux mac (32)
- linux ip地址 (34)