一个线程池的理解

news2025/1/16 0:54:07

最近看到一个线程池,写的实在太好,于是想深入理解一下。原始代码出处:GitHub - Ahajha/CTPL: Modern and efficient C++ Thread Pool Library

由于平时的工程一般只支持到C++11,而拿到的代码应该是在C++20下才能编译通过,因此也做了一些修改,需要原始码的可去github上自行下载。

先出测试代码:

#include "ThreadPool.h"


void func4444()
{
	printf("[%s]thread_id[%s]:func4444begin--------\n", current_time().c_str(),get_curr_thread_id().c_str());
	::Sleep(2000);
	printf("[%s]thread_id[%s]:func4444end..--------\n", current_time().c_str(), get_curr_thread_id().c_str());

}

void func5555(int v)
{
	printf("[%s]thread_id[%s]:func5555begin----参数: %d----\n", current_time().c_str(), get_curr_thread_id().c_str(), v);
	::Sleep(3000);
	printf("[%s]thread_id[%s]:func5555end..----参数: %d----\n", current_time().c_str(), get_curr_thread_id().c_str(), v);

}

int main()
{
	printf("[%s]main_thread_id:[%s]\n", current_time().c_str(), get_curr_thread_id().c_str());
	ctpl::thread_pool p;
	int intValue = 42;

	p.add_func("func4444", func4444);
	p.add_func("func5555", func5555, intValue);
	while (true)
	{
		::Sleep(500);
	}
}

测试结果如下:

可以看到,加入到线程池的过程是在主线程中进行的,实际运行的都是在工作线程中完成。

关键代码

1、加入到任务队列

		template<typename F, typename... Args>
		void add_func(const char* name, F && f, Args&&... args)
		{
			auto pck = std::make_shared<std::packaged_task<decltype(f(args...))()>>(
				std::bind(std::forward<F>(f), std::forward<Args>(args)...)
				);
			printf("[%s]thread_id[%s]:add_func: %s\n", current_time().c_str(), get_curr_thread_id().c_str(), name);
			this->push_func(std::make_unique<std::function<void()>>(
				[pck, name] {
				if (strlen(name) > 0)
					printf("[%s]thread_id[%s]:sche_trace_func %s enter ...\n", current_time().c_str(), get_curr_thread_id().c_str(), name);
				(*pck)();
				if (strlen(name) > 0)
					printf("[%s]thraed_id[%s]:sche_trace_func %s exit .\n", current_time().c_str(), get_curr_thread_id().c_str(), name);
			})
				);
		}

pck是一个包装指针,(*pck)()会执行被包装任务中的函数 f,并传入参数 args...,因此(*pck)();实际就是执行f(args)

由于(*pck)()是在匿名函数,当执行this->push_func时,相当于把这个匿名函数存于func_tasks中,func_tasks是一个线程安全的队列。此时并未真正执行函数,只是暂存起来。注意,detail::atomic_queue<std::unique_ptr<std::function<void()>>> func_tasks;看到队列存储的函数类型是固定的,即无输入且无输出的函数类型,即我们这儿的匿名函数。

    void thread_pool::push_func(std::unique_ptr<std::function<void()>> &task)
    {
        this->func_tasks.push(std::move(task));
        this->signal_cv.notify_one();
    }

当通过push_func将匿名函数加入进队列func_tasks后,会给工作线程发送通知,激活空闲的工作线程以执行此匿名函数,并从此匿名函数中执行对应的函数f(args)

2、从任务队列中取任务并执行

void thread_pool::emplace_thread()
	{
		this->stop_flags.emplace_back(std::make_shared<std::atomic<bool>>(false));

		// The main loop for the thread. Grabs a copy of the pointer
		// to the stop flag.
		const auto stop_flag = this->stop_flags.back();
		this->threads.emplace_back([this, stop_flag]
		{
			printf("[%s]thread_id:[%s]\n", current_time().c_str(), get_curr_thread_id().c_str());
			std::atomic<bool>& stop = *stop_flag;

			// Used to store new tasks.
			std::unique_ptr<std::function<void()>> task;

			// True if 'task' currently has a runnable task in it.
			auto has_new_task = this->func_tasks.pop(task);
			while (true)
			{
				// If there is a task to run
				while (has_new_task)
				{
					// Run the task
					(*task)();

					// Delete the task
					task.reset();

					// The thread is wanted to stop, return even
					// if the queue is not empty yet
					if (stop)
					{
						return;
					}

					// Get a new task
					has_new_task = this->func_tasks.pop(task);
				}

				// At this point the queue has run out of tasks, wait here for more.

				// Thread is now idle.
				// If all threads are idle, notify any waiting in wait().
				if (++this->_n_idle == this->size())
				{
					std::lock_guard<std::mutex> lock(this->waiter_mut);
					this->waiter.notify_all();
				}

				std::unique_lock<std::mutex> lock(this->signal_mut);

				// While the following evaluates to true, wait for a signal.
				this->signal_cv.wait(lock, [this, &task, &has_new_task, &stop]()
				{
					// Try to get a new task. This will fail if the thread was
					// woken up for another reason (stopping or resizing), or
					// if another thread happened to grab the task before this
					// one got to it.
					has_new_task = this->func_tasks.pop(task);

					// If there is a new task or the thread is being told to stop,
					// stop waiting.
					return has_new_task || this->done || stop;
				});

				// Thread is no longer idle.
				--this->_n_idle;

				// if the queue is empty and it was able to stop waiting, then
				// that means the thread was told to stop, so stop.
				if (!has_new_task)
				{
					return;
				}
			}
		});
	}

一般emplace_thread在主线程中被调用,通常调用一次就会多一个工作线程,this->threads.emplace_back([this, stop_flag]{}这一行代码完成了一个工作线程的开启,工作的内容即是大括号内的匿名函数。

这个函数内部有两层循环,最内部的循环总是判断是否有新的任务,有的话通过(*task)()执行之前包装的匿名函数,再由那个被包装的匿名函数调用真正需要执行的函数。执行完后取下一个新线程,队列中若没有新线程则跳出内层的while循环,在外层等待新任务的信号。

三、小结

前面两个是线程池最关键的代码段,有了这个基础,再顺着代码调试一下,基本上就清晰了。当然,这里面还有关于C++11的用法,模板相关的同样比较绕人,不过那些小知识点可以通过小的练习完成,不会对理解这个程序造成太大的困扰。

后面再找时间做一个扩展,看如何在这个基础上进行定时器的逻辑推演,其实原先拿到的程序是定时器相关的东西,只不过觉得那个太复杂,于是花了很大的精力将其去除,保留核心。

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

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

相关文章

Kubernetes(K8s)技术解析

1. K8s简介 Kubernetes&#xff08;简称K8s&#xff09;是一个开源的容器编排平台&#xff0c;旨在简化容器化应用程序的部署、扩展和管理。为开发者和运维人员提供了丰富的功能和灵活的解决方案&#xff0c;帮助他们更轻松地构建、部署和管理云原生应用程序。以下是关于Kubern…

3.Swagger整合

一、引入相关依赖 <!-- 图像化依赖 --> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version> </dependency> <!--引入swagger2依赖 --> <d…

SQL注入---盲注

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.盲注概述 注是一种SQL注入攻击的形式&#xff0c;在这种攻击中&#xff0c;攻击者向目标应用程序发送恶意注入代码&#xff0c;然后通过观察应用程序的响应来推断出数据库中的信息。与常规的…

C++中二叉搜索树的模拟实现(二叉搜索树是map,set的底层原理)

搜索二叉树 定义 搜索二叉树:左子树小于根,右子树大于根.搜索二叉树的中序序列是升序的.所以对于二叉树而言,它的左子树和右子数都是二叉搜索树 下图就是二叉搜索树 二叉搜索树的性质: 二叉搜索树的中序遍历出的数据是有序的,并且二叉树搜索树在查找某个数的时候,一般情况下…

Outlook会议邀请邮件在答复后就不见了

时常会有同事找到我说&#xff0c;Outlook答复会议邀请邮件后收件箱就找不到会议邀请的邮件了。 这其实是Outlook的的一个机制&#xff0c;会把应答后的会议邀请邮件从收件箱自动删除&#xff0c;到已删除的邮件那里就能找到。如果不想要自动删除&#xff0c;改一个设置即可。…

kafka 高吞吐设计分析

说明 本文基于 kafka 2.7 编写。author blog.jellyfishmix.com / JellyfishMIX - githubLICENSE GPL-2.0 概括 支撑 kafka 高吞吐的设计主要有以下几个方面: 网络 nio 主从 reactor 设计模式 顺序写。 零拷贝。 producer producer 开启压缩后是批量压缩&#xff0c;bro…

k8s部署微服务例子

一、部署服务 需要部署minio、nacos、mysql、consul、elasticsearch、视频解析服务、nfs、skywalking-oap及ui。 二、三个微服务程序 minio服务解析视频-》上传到minio进行存储&#xff0c;构造领域对象信息保存到hive&#xff08;hive on spark&#xff09;异步处理-》元数据…

c#程序报错引用无效解决办法之一:检查引用的文件路径

直接右键然后打开本地 打开这个.csproj文件&#xff0c;直接对着路径看看里面的路径对不对。 一般是很多人一起开发&#xff0c;然后这个文件路径被推送上来的问题

前端三剑客 —— CSS (第四节)

目录 内容回顾&#xff1a; 1.常见样式 2.特殊样式 特殊样式 过滤效果 动画效果 动画案例&#xff1a; 渐变效果 其他效果&#xff1a; 多列效果 字体图标&#xff08;icon&#xff09; 内容回顾&#xff1a; 1.常见样式 text-shadow x轴 y轴 阴影的模糊程度 阴影的…

kubectl explain资源文档命令

学习并使用了一段时间的kubernetes&#xff0c;发现对k8s还是了解甚少&#xff0c;于是利用上下班通勤的时间又去B站看一些大佬的视频&#xff0c;又来重学巩固一遍知识&#xff0c;并做些记录。 之前在学习使用过程中未成了解过explain这个命令&#xff0c;因为自己部署的版本…

一站式知识库服务平台真的有那么好用吗?看完你就懂了

在快速发展的信息化社会&#xff0c;我们经常会听到“知识就是力量”的这句话&#xff0c;而一个一站式的知识库服务平台就是这样一把“开启力量之门”的钥匙。那么&#xff0c;这把钥匙真的有那么好用吗&#xff1f;让我们一起探讨一下。 首先&#xff0c;“一站式”可能已经解…

vite.config.js

Vue3vite vite和webpack区别&#xff1f; 1.vite服务器启动速度比webpack快&#xff0c;由于vite启动的时候不需要打包&#xff0c;也就无需分析模块依赖、编译&#xff0c;所以启动速度非常快。当浏览器请求需要的模块时&#xff0c;再对模块进行编译&#xff0c;这种按需动态…

软件杯 深度学习中文汉字识别

文章目录 0 前言1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xf…

【T5中的激活函数】GLU Variants Improve Transformer

【mT5中的激活函数】GLU Variants Improve Transformer 论文信息 阅读评价 Abstract Introduction Gated Linear Units (GLU) and Variants Experiments on Text-to-Text Transfer Transformer (T5) Conclusion 论文信息 名称内容论文标题GLU Variants Improve Transfo…

AI绘画:实例-利用Stable Diffusion ComfyUI实现多图连接:区域化提示词与条件设置

在Stable Diffusion ComfyUI中&#xff0c;有一种高级技巧可以让用户通过细致的区域化提示词来控制图像的不同部分&#xff0c;从而实现多图连接的效果。这种方法允许艺术家在同一画布上展现多个场景&#xff0c;创造出富有层次和故事性的图像。以下是实现这一效果的详细步骤。…

【与C++的邂逅之旅】--- 内联函数 auto关键字 基于范围的for循环 nullptr

关注小庄 顿顿解馋૮(˶ᵔ ᵕ ᵔ˶)ა 博主专栏&#xff1a; &#x1f4a1; 与C的邂逅之旅 &#x1f4a1; 数据结构之旅 上篇我们了解了函数重载和引用&#xff0c;我们继续学习有关C的一些小语法— 内联函数&#xff0c;auto关键字&#xff0c;基于范围的for循环以及 nullptr&…

element-ui empty 组件源码分享

今日简单分享 empty 组件的源码实现&#xff0c;主要从以下三个方面&#xff1a; 1、empty 组件页面结构 2、empty 组件属性 3、empty 组件 slot 一、empty 组件页面结构 二、empty 组件属性 2.1 image 属性&#xff0c;图片地址&#xff0c;类型 string&#xff0c;无默认…

Docker 安装 | 部署MySQL 8.x 初始设置

1、准备工作 如果不想看前面的废话请直接右边目录跳到 运行容器 处 默认你已经有 docker 环境。 Windows 推荐 Docker Desktop &#xff08;下载地址&#xff09;并基于 WSL2 运行 Docker 环境 mac 推荐 Orbstack &#xff08;下载地址&#xff09;&#xff08;这个很节省资源&…

C++之函数提高(HM)

目录 1.函数默认参数&#xff08;缺省参数&#xff09; 2.占位参数 3.函数重载 4.类和对象--封装 &#xff08;1&#xff09;圆类&#xff1a; &#xff08;2&#xff09;访问权限 &#xff08;3&#xff09;struct&&class &#xff08;4&#xff09;立方体类的…

适用于车载设备无钥匙进入系统汽车用晶振FA-238A

汽车用晶振FA-238A是一款适用于车载设备无钥匙进入系统的耐高温晶振。汽车用晶振FA-238A是爱普生推出一的款MHz表贴式晶体单元&#xff0c;具有很好的预率性能&#xff0c;符合AEC-0200标准&#xff0c;其封装尺寸仅为3.2x2.5x0.7mm&#xff0c;工作温度范围在-40℃~125℃之间&…