Github开源项目源码阅读(progschjThreadPool)

项目地址:https://github.com/progschj/ThreadPool

项目源码:

#ifndef THREAD_POOL_H
#define THREAD_POOL_H

include <vector>

include <queue>

include <memory>

include <thread>

include <mutex>

include <condition_variable>

include <future>

include <functional>

include <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;

};

// 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&lt;std::mutex&gt; lock(this-&gt;queue_mutex);this-&gt;condition.wait(lock,[this]{ return this-&gt;stop || !this-&gt;tasks.empty(); });if(this-&gt;stop &amp;&amp; this-&gt;tasks.empty())return;task = std::move(this-&gt;tasks.front());this-&gt;tasks.pop();}task();}});

}

// 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&lt; std::packaged_task&lt;return_type()&gt; &gt;(std::bind(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(args)...));std::future&lt;return_type&gt; res = task-&gt;get_future();
{std::unique_lock&lt;std::mutex&gt; lock(queue_mutex);// don't allow enqueueing after stopping the poolif(stop)throw std::runtime_error(&quot;enqueue on stopped ThreadPool&quot;);tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;

}

// 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();
}

endif

用法示例:

#include <iostream>
#include <vector>
#include <chrono>

include "ThreadPool.h"

int main()
{

ThreadPool pool(4);
std::vector&lt; std::future&lt;int&gt; &gt; results;for(int i = 0; i &lt; 8; ++i) {results.emplace_back(pool.enqueue([i] {std::cout &lt;&lt; &quot;hello &quot; &lt;&lt; i &lt;&lt; std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout &lt;&lt; &quot;world &quot; &lt;&lt; i &lt;&lt; std::endl;return i*i;}));
}for(auto &amp;&amp; result: results)std::cout &lt;&lt; result.get() &lt;&lt; ' ';
std::cout &lt;&lt; std::endl;return 0;

}

类成员变量

// 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;
  • workers:存储启动的线程对象
  • tasks:存储提交到线程池的任务
  • queue_mutex:互斥锁,保证任务队列操作的原子性
  • condition:配合互斥锁,实现线程通信
  • stop:线程池是否停止

构造函数

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&lt;std::mutex&gt; lock(this-&gt;queue_mutex);this-&gt;condition.wait(lock,[this]{ return this-&gt;stop || !this-&gt;tasks.empty(); });if(this-&gt;stop &amp;&amp; this-&gt;tasks.empty())return;task = std::move(this-&gt;tasks.front());this-&gt;tasks.pop();}task();}});

}

构造函数干了这几件事:

  1. 根据构造线程池对象时传入的参数,初始化线程数量threads

  2. 将stop变量初始化为false

  3. 根据threads,创建相应数量的线程对象,存储到workers中。C++中的线程是创建即启动,所以这些线程也同时开始运行。运行时行为模式为以下三种:

    1. 无任务线程池未终止:阻塞
    2. 无任务线程池已终止:返回。线程函数返回后,线程结束,资源被释放。
    3. 有任务:执行任务。

任务入队函数

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&lt; std::packaged_task&lt;return_type()&gt; &gt;(std::bind(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(args)...));std::future&lt;return_type&gt; res = task-&gt;get_future();
{std::unique_lock&lt;std::mutex&gt; lock(queue_mutex);// don't allow enqueueing after stopping the poolif(stop)throw std::runtime_error(&quot;enqueue on stopped ThreadPool&quot;);tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;

}

该函数将任务提交到tasks中。

template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>

接收任务,也就是传入的可调用对象的类型、可调用对象、参数类型列表和参数列表。

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();
  1. 将可调用对象及实参使用绑定器绑定,返回一个无参的可调用对象;
  2. 将绑定器返回的无参的可调用对象包装成一个packaged_task​对象;
  3. 使用packaged_task​对象创建智能指针shared_ptr​。
  4. packaged_task​对象获取一个std::future​对象

补充知识:

  1. std::packaged_task

std::packaged_task​ 是 C++ 标准库中用于封装可调用对象的类模板。它允许将一个函数(或其他可调用对象)及其参数包装起来,并与一个 std::future​ 对象关联。

  • 模板参数:std::packaged_task<R(Args)>std::packaged_task​接收的模版参数是函数签名R(Args)​,如同std::function<R(Args)>​一样。
  1. std::future

是 C++ 标准库中用于异步操作的工具。它表示某个异步任务完成后的返回值。这里std::future​用于接收std::package_task​的返回值。

{std::unique_lock<std::mutex> lock(queue_mutex);
    // don't allow enqueueing after stopping the poolif(stop)throw std::runtime_error(&quot;enqueue on stopped ThreadPool&quot;);tasks.emplace([task](){ (*task)(); });

}

花括号是为了让互斥锁出作用域时自动释放。如果线程池终止,就抛出异常,否则将任务入队。

condition.notify_one();
return res;

最后,通知阻塞的线程执行任务,返回。

析构函数

inline ThreadPool::~ThreadPool()
{{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for(std::thread &worker: workers)worker.join();
}

将线程池运行状态转为终止。唤醒所有阻塞的线程,阻塞的线程被唤醒后,处于线程池终止无任务状态,会被回收。正在执行任务的线程执行完任务后,也会进入线程池终止无任务状态,被回收。worker.join();​使得主线程等待所有子线程执行完毕。

小结

该线程池只实现了固定线程数量的模式。可以考虑增加动态增减线程的功能。

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

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

相关文章

多端响应式

重要!响应式开发 目录重要!响应式开发是什么?怎么实现?媒体媒体查询断点是什么适配方案栅格布局响应式栅格系统实例 是什么? 多终端显示不同,更好看 PC,ipad,手机适配 怎么实现? 媒体查询,断点: 视口宽不同,布局不同 移动端??? 320-480端口 rem 不考虑大尺寸,更…

公共图床-OIER试炼场

公共图床-OIER试炼场 如何使用? 1. 使用浏览器直接访问 https://api-cdn.u1128333.nyat.app:35910/2. 通过插件访问 插件下载点我 MIT License, ©2025 Chan. 在浏览器扩展中添加即可使用。

32. 样式表

一、样式表为了美化窗口或控件的外观,可以通过窗口或控件的调色板给窗口或控件按照角色和分组设置颜色,还可以对窗口或控件的每个部分进行更细致的控制,这涉及窗口或控件的样式表(Qt style sheets, QSS),它是从 HTML 的层叠样式表(cascading style sheets, CSS)演化而来…

TIA SCL编程清除字符串中所有的空格

今天做一个小的练习,这是2025年第一个记录的学习笔记。 在IA新建一个FC,名字叫做TrimSpace,建立以下内部变量:写一段SCL代码: #len := LEN(#str_in);#str_trim_out := ;FOR #i := 1 TO #len DO   IF MID(IN := #str_in, L := 1, P := #i) <> THEN   …

计算几何相关

打算慢慢记一下扫描线,辛普森法和凸包。扫描线 扫描线直接照搬了...这篇主要是给之后的积分和凸包之类留的。 就是用线段树解决计算几何问题。 板梯 如何处理平面内一群矩形的面积交? 考虑用面积的朴素定义,\(S=ab\),相当于一堆面积合到一起就是 \(b\) 个可以不同的 \(a\) …

树状数组(学习笔记)

例题一:P3374 【模板】树状数组 1 例题二:P3368 【模板】树状数组 2 作用:特征: 一个多用于区间修改,和单点查询。或区间查询单点修改的数据结构,其代码量较少,比较好写。 区别: 它与线段树的功能差不多,但线段树的可拓展性更强。也就是说:树状数组能做的,线段树都能…

Centos根目录扩容

CentOS磁盘在根目录下扩容(无卷组情况下)查看磁盘分区情况,sda3挂载在根目录下,新扩容增加的sda4挂在在/data下现在想把sda4同样挂载在根目录下解决方法:删除sda3,再重建sda3(注意:删除后不要退出,紧接着重建,注意重建后的起始位置) 删除sda3然后重建输入下面那个命…

U455764 The Rotation Game

U455764 The Rotation Game 题目理解 本题要求移动\(A-H\)中的一列或一行,使其整个一行和一列的数字移动,使最后的中间8个的数字相同。求最少需要移动的步数和它的操纵顺序思路 1.本题可以很显然的想到用 \(BFS\) 来枚举执行不同字母操作后结果,但每 \(BFS\) 一次就会增加八…

BUU SQL COURSE 2

BUU SQL COURSE 2 BUU SQL COURSE 2 和前面那题差不多,但是注一下找到的登录成功但是显示 fault。 注一下别的看一下,在另一个数据库里找到了 flag。本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/18682313

PotPlayer 配置安装

目录一、下载1、官网链接2、微软商店 Microsoft Store二、安装1、双击安装包2、选择字体3、安装向导下一步4、接收许可协议5、选择组件及关联6、选择安装位置7、硬解选项三、设置1、关闭自动更新2、左键单双击设置3、视频下自动隐藏3.1、效果对比4、播放信息显示设置4.1、效果5…

2024年春秋杯网络安全联赛冬季赛部分wp

部分附件下载地址: https://pan.baidu.com/s/1Q6FjD5K-XLI-EuRLhxLq1Q 提取码: jay1 Misc day1-简单算术 根据提示应该是异或下载文件是一个字符串,写个代码字符串异或解密,由于需要密钥,所以先对单字节密钥进行爆破解密 爆破出flag代码如下: cipher_text = "ys~xdg/…

大夏龙雀DX-WF25(ESP32C2)蓝牙WIFI模块试用体验

模块尺寸跟引脚兼容常见的ESP8266 12-F模块,使用非常简单,vcc接3.3v,gnd接地,rx连接ch340的tx,tx连接ch340的rx即可。 如果需要通过urat下载程序,只需要把io9的引脚接地就行(下载完程序记得断开io9跟gnd的连接,否则程序无法运行)。芯片用的是ESP32C2(ESP8684),很遗…