线程池-手写线程池C++11版本(生产者-消费者模型)

本项目是基于C++11的线程池。使用了许多C++的新特性,包含不限于模板函数泛型编程、std::future、std::packaged_task、std::bind、std::forward完美转发、std::make_shared智能指针、decltype类型推断、std::unique_lock锁等C++11新特性功能。


本项目有一定的上手难度。推荐参考系列文章

C++11实用技术(一)auto与decltype的使用

C++11实用技术(二)std::function和bind绑定器

C++11实用技术(三)std::future、std::promise、std::packaged_task、async

C++ operator关键字的使用(重载运算符、仿函数、类型转换操作符)

右值引用以及move移动语义和forward 完美转发

C++标准库中的锁lock_guard、unique_lock、shared_lock、scoped_lock、recursive_mutex


代码结构

本项目线程池功能分以下几个函数去实现:

threadpool.init(isize_t num);设置线程的数量
threadpool::get(TaskFuncPtr& task);读取任务队列中的任务
threadpool::run();通过get()读取任务并执行
threadpool.start(); 启动线程池,并通过run()执行任务
threadpool.exec();封装任务到任务队列中
threadpool.waitForAllDone();等待所有任务执行完成
threadpool.stop();分离线程,释放内存

threadpool.init

init的功能是初始化线程池,主要是设置线程的数量到类的成员变量中。

bool ZERO_ThreadPool::init(size_t num)
{std::unique_lock<std::mutex> lock(mutex_);if (!threads_.empty()){return false;}threadNum_ = num;return true;
}

threadNum_:保存线程的数量,在init函数中被赋值

此处使用unique_lock或lock_guard的加锁方式都能实现自动加锁和解锁。但是unique_lock可以进行临时解锁和再上锁,而lock_guard不行,特殊情况下还是必须使用unique_lock(用到条件变量的情况)。(lock_guard比较简单,相对来说性能要好一点)

threadpool::get

从任务队列中获取获取任务,这里其实就是我们的消费者模块

bool ZERO_ThreadPool::get(TaskFuncPtr& task)
{std::unique_lock<std::mutex> lock(mutex_);if (tasks_.empty()) //判断任务是否存在{//要终止线程池   bTerminate_设置为true,任务队列不为空condition_.wait(lock, [this] { return bTerminate_ || !tasks_.empty(); });}if (bTerminate_)return false;if (!tasks_.empty()){task = std::move(tasks_.front());  // 使用了移动语义tasks_.pop(); //释放资源,释放一个任务return true;}return false;
}
  • 条件变量condition_.wait(lock, [this] { return bTerminate_ || !tasks_.empty(); });是需要一直等待条件完成才退出。即任务终止,或者任务队列不为空时,就会退出条件变量的阻塞状态,继续执行下面的逻辑。

  • task = std::move(tasks_.front()); 使用了移动语义,将 tasks_.front() 的内容移动到了 task 中。可以减少内容拷贝。移动完之后tasks_.front() 的内容会变为未指定的状态,所以直接pop掉就好了。

threadpool::run

这里是运行我们的任务部分。包括调用get在任务队列中获取任务,以及执行任务。

void ZERO_ThreadPool::run()  // 执行任务的线程
{//调用处理部分while (!isTerminate()) // 判断是不是要停止{TaskFuncPtr task;bool ok = get(task);        // 读取任务if (ok){++atomic_;try{if (task->_expireTime != 0 && task->_expireTime < TNOWMS){//如果设置了超时,并且超时了,就需要执行本逻辑//超时任务,本代码未实现,有需要可实现在此处}else{task->_func();  // 执行任务}}catch (...){}--atomic_;}}}
}

atomic_:运行一个任务,该参数+1;执行完毕,该参数-1。这里是为了待会停止线程池时判断是否还有运行中的任务(未完成的线程)。

threadpool.start

创建线程,并把线程池存入vector中,后面释放线程池时,好一一释放线程。

bool ZERO_ThreadPool::start()
{std::unique_lock<std::mutex> lock(mutex_);if (!threads_.empty()){return false;}for (size_t i = 0; i < threadNum_; i++){threads_.push_back(new thread(&ZERO_ThreadPool::run, this));}return true;
}

threads_.push_back(new thread(&ZERO_ThreadPool::run, this));创建线程,线程的回调函数为run。

threadpool.exec

exec是将我们的任务存入任务队列中,这段代码是本项目最难的,用了很多C++的新特性。

/*template <class F, class... Args>它是c++里新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数auto exec(F &&f, Args &&... args) -> std::future<decltype(f(args...))>std::future<decltype(f(args...))>:返回future,调用者可以通过future获取返回值返回值后置*/template <class F, class... Args>auto exec(int64_t timeoutMs, F&& f, Args&&... args) -> std::future<decltype(f(args...))>//接受一个超时时间 `timeoutMs`,一个可调用对象 `f` 和其它参数 `args...`,并返回一个 `std::future` 对象,该对象可以用于获取任务执行的结果。{int64_t expireTime = (timeoutMs == 0 ? 0 : TNOWMS + timeoutMs);  // 根据超时时间计算任务的过期时间 `expireTime`,如果超时时间为 0,则任务不会过期。//定义返回值类型using RetType = decltype(f(args...));  // 使用 `decltype` 推导出 `f(args...)` 的返回值类型,并将其定义为 `RetType`(这里的using和typedef功能一样,就是为一个类型起一个别名)。// 封装任务 使用 `std::packaged_task` 将可调用对象 `f` 和其它参数 `args...` 封装成一个可执行的函数,并将其存储在一个 `std::shared_ptr` 对象 `task` 中。auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));TaskFuncPtr fPtr = std::make_shared<TaskFunc>(expireTime);  // 封装任务指针,设置过期时间 创建一个 `TaskFunc` 对象,并将任务的过期时间 `expireTime` 传递给它。fPtr->_func = [task]() {  // 具体执行的函数 将封装好的任务函数存储在 `TaskFunc` 对象的 `_func` 成员中,该函数会在任务执行时被调用。(*task)();};std::unique_lock<std::mutex> lock(mutex_);tasks_.push(fPtr);              // 将任务插入任务队列中condition_.notify_one();        // 唤醒阻塞的线程,可以考虑只有任务队列为空的情况再去notifyreturn task->get_future();; //返回一个 `std::future` 对象,该对象可以用于获取任务执行的结果。}

使用了可变参数模板函数。
tasks_:保存任务的队列
condition_.notify_one():保存一个任务唤醒一个条件变量
std::future : 异步指向某个任务,然后通过future特性去获取任务函数的返回结果。
std::bind:将参数列表和函数绑定,生成一个新的可调用对象
std::packaged_task:将任务和feature绑定在一起的模板,是一种封装对任务的封装。

本函数用到了泛型编程模板函数,输入参数有3个:一个超时时间 timeoutMs,一个可调用对象 f 和参数 args...。采用返回值后置的方式返回一个std::future对象。这里采用返回值后置是为了方便使用decltype(f(args…)推导数据类型。

auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward(f), std::forward(args)…));是将我们传进来的任务函数和参数bind成一个对象,这个对象可以看作是一整个函数,其返回值就是RetType 类型,并且没有输入参数。所以用std::packaged_task<RetType()>这样的格式来打包封装。封装好的对象用智能指针(std::make_shared)来管理。

同时还要创建一个TaskFunc的对象,同样用智能指针管理,这个对象包括两项内容,一个就是超时时间,一个就是我们封装好的task对象。
通过TaskFuncPtr fPtr = std::make_shared(expireTime);和fPtr->_func = task {(*task)();};两条代码将这两项传进去。

最后会通过task->get_future()返回我们任务函数执行的结果返回值。

threadpool.waitForAllDone

等待所有任务执行完成。

bool ZERO_ThreadPool::waitForAllDone(int millsecond)
{std::unique_lock<std::mutex> lock(mutex_);if (tasks_.empty() && atomic_ == 0)return true;if (millsecond < 0){condition_.wait(lock, [this] { return tasks_.empty() && atomic_ == 0; });return true;}else{return condition_.wait_for(lock, std::chrono::milliseconds(millsecond), [this] { return tasks_.empty() && atomic_ == 0; });}
}

使用条件变量来等待任务执行完成。支持超时执行功能。

此处unique_lock的使用是必须的: 条件变量condition_在wait时会进行unlock再进入休眠, lock_guard并无该操作接口

threadpool.stop

终止线程池。会调用waitForAllDone等待所有任务执行完成再终止。

void ZERO_ThreadPool::stop()
{{std::unique_lock<std::mutex> lock(mutex_);bTerminate_ = true;condition_.notify_all();}waitForAllDone();for (size_t i = 0; i < threads_.size(); i++){if (threads_[i]->joinable()){threads_[i]->join();}delete threads_[i];threads_[i] = NULL;}std::unique_lock<std::mutex> lock(mutex_);threads_.clear();
}

通过join等线程执行完成后才返回。

主函数调用

class Test
{
public:int test(int i) {cout << _name << ", i = " << i << endl;Sleep(1000);return i;}void setName(string name) {_name = name;}string _name;
};
void test3() // 测试类对象函数的绑定
{ZERO_ThreadPool threadpool;threadpool.init(2);threadpool.start(); // 启动线程池Test t1;Test t2;t1.setName("Test1");t2.setName("Test2");auto f1 = threadpool.exec(std::bind(&Test::test, &t1, std::placeholders::_1), 10);auto f2 = threadpool.exec(std::bind(&Test::test, &t2, std::placeholders::_1), 20);cout << "t1 " << f1.get() << endl;cout << "t2 " << f2.get() << endl;threadpool.stop();
}
int main()
{test3(); // 测试类对象函数的绑定cout << "main finish!" << endl;return 0;
}

运行结果:
在这里插入图片描述

本项目完整代码下载地址基于C++11的线程池

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

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

相关文章

计算机网络—TCP

这里写目录标题 TCP头格式有哪些为什么需要TCP&#xff0c;TCP工作在哪什么是TCP什么是TCP连接如何确定一个TCP连接TCP和UDP的区别&#xff0c;以及场景TCP和UDP能共用一个端口&#xff1f;TCP的建立TCP三次握手过程为什么是三次握手、不是两次、四次why每次建立连接&#xff0…

3.1 计算机网络和网络设备

数据参考&#xff1a;CISP官方 目录 计算机网络基础网络互联设备网络传输介质 一、计算机网络基础 1、ENIAC&#xff1a;世界上第一台计算机的诞生 1946年2月14日&#xff0c;宾夕法尼亚大学诞生了世界上第一台计算机&#xff0c;名为电子数字积分计算机&#xff08;ENIAC…

Netty框架自带类DefaultEventExecutorGroup的作用,用来做业务的并发

一、DefaultEventExecutorGroup的用途 DefaultEventExecutorGroup 是 Netty 框架中的一个类&#xff0c;用于管理和调度事件处理器&#xff08;EventExecutor&#xff09;的组。在 Netty 中&#xff0c;事件处理是通过多线程来完成的&#xff0c;EventExecutor 是处理事件的基…

2023年中期奶粉行业分析报告(京东数据开放平台)

根据国家统计局和民政部数据公布&#xff0c;2022年中国结婚登记数创造了1980年&#xff08;有数据公布&#xff09;以来的历史新低&#xff0c;共计683.3万对。相较于2013年巅峰时期的数据&#xff0c;2022年全国结婚登记对数已接近“腰斩”。 2023年“520”期间的结婚登记数…

【MySQL】

这里写目录标题 MySQL架构一条sql执行流程MySQL数据存放电脑位置ibd文件结构行溢出是什么MySQL行记录存储格式索引为什么InnoDB选择B树作为索引数据结构什么时候需要创建索引优化索引方法InnoDB内部怎么存储数据B 树如何进行查询聚簇索引和二级索引为什么MySQL要采用B树作为索引…

【Spring Boot】Spring Boot项目的创建和文件配置

目录 一、为什么要学Spring Boot 1、Spring Boot的优点 二、创建Spring Boot项目 1、创建项目之前的准备工作 2、创建Spring Boot项目 3、项目目录的介绍 4、安装Spring Boot快速添加依赖的插件 5、在项目中写一个helloworld 三、Spring Boot的配置文件 1、配置文件的…

ELK 企业级日志分析系统(二)

目录 ELK Kiabana 部署&#xff08;在 Node1 节点上操作&#xff09; 1&#xff0e;安装 Kiabana 2&#xff0e;设置 Kibana 的主配置文件 3&#xff0e;启动 Kibana 服务 4&#xff0e;验证 Kibana 5&#xff0e;将 Apache 服务器的日志&#xff08;访问的、错误的&#x…

知识图谱实战应用23-【知识图谱的高级用法】Neo4j图算法的Cypher查询语句实例

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用23-【知识图谱的高级用法】Neo4j图算法的Cypher查询语句实例,Neo4j图算法是一套在Neo4j图数据库上运行的算法集合。这些算法专门针对图数据结构进行设计,用于分析、查询和处理图数据。图算法可以帮助我们发现图中的模…

【Azure】office365邮箱测试的邮箱账号因频繁连接邮箱服务器而被限制连接 引起邮箱显示异常

azure微软office365邮箱会对频繁连接自身邮箱服务器的IP地址进行&#xff0c;连接邮箱服务器IP限制&#xff0c;也就是黑名单&#xff0c;释放时间不确定&#xff0c;但至少一天及以上。 解决办法&#xff0c;换一个IP&#xff0c;或者新注册一个office365邮箱再重试。 以下是…

使用 API Gateway Integrator 在 Quarkus 中实施适用于 AWS Lambda 的 OpenAPI

AWS API Gateway 集成使得使用符合 OpenAPI 标准的 Lambda Function 轻松实现 REST API。 关于开放API 它是一个 允许以标准方式描述 REST API 的规范。 OpenAPI规范 (OAS) 为 REST API 定义了与编程语言无关的标准接口描述。这使得人类和计算机都可以发现和理解服务的功能&am…

中介者模式(C++)

定义 用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖)&#xff0c;从而使其耦合松散(管理变化)&#xff0c;而且可以独立地改变它们之间的交互。 应用场景 在软件构建过程中&#xff0c;经常会出现多个对象…

漫画算法做题笔记

诸神缄默不语-个人CSDN博文目录 哦这是我三年前写的&#xff0c;我现在Java语法都快忘光了…… 反正之前的博文也发一下好了。这个因为我当年是用有道云笔记而不是直接用CSDN编辑器写的&#xff0c;所以后面有些内容写乱了&#xff0c;因为我现在猛的一看有点看不懂&#xff0…

C++11之右值引用

C11之右值引用 传统的C语法中就有引用的语法&#xff0c;而C11中新增了的 右值引用&#xff08;rvalue reference&#xff09;语法特性&#xff0c;所以从现在开始我们之前学习的引用就叫做左值引用&#xff08;lvalue reference&#xff09;。无论左值引用还是右值引用&#…

【TypeScript】初识TypeScript和变量类型介绍

TypeScript 1&#xff0c;TypeScript是什么?2&#xff0c;类型的缺失带来的影响3&#xff0c;Ts搭建环境-本博主有专门的文章专说明这个4&#xff0c;使用tsc对ts文件进行编译5&#xff0c;TS运行初体验简化Ts运行步骤解决方案1解决方案2&#xff08;常见&#xff09; 开始学习…

Apache Paimon 学习笔记

本博客对应于 B 站尚硅谷教学视频 尚硅谷大数据Apache Paimon教程&#xff08;流式数据湖平台&#xff09;&#xff0c;为视频对应笔记的相关整理。 1 概述 1.1 简介 Flink 社区希望能够将 Flink 的 Streaming 实时计算能力和 Lakehouse 新架构优势进一步结合&#xff0c;推…

Centos更换网卡名称为eth0

Centos更换网卡名称为eth0 已安装好系统后需要修改网卡名称为eth0 编辑配置文件将ens33信息替换为eth0,可在vim命令模式输入%s/ens33/eth0/g替换相关内容 修改内核文件,添加内容:net.ifnames=0 biosdevname=0 [root@nova3 ~]# vim /etc/default/grub 使用命令重新生成g…

【JAVA】有关时间的操作在编程中如何实现?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️初识JAVA】 文章目录 前言Date 类Date 类方法Data的缺陷实例获取当前日期时间日期比较java中设置date数据的显示格式 前言 在许多应用程序中&#xff0c;日期和时间的处理是必不可少的。Java提供了一…

用Shap-E生成3D模型

Shap-E 是 OpenAI 开发的突破性模型&#xff0c;它使用文本或图像作为输入生成一系列 3D 对象&#xff0c;以其创新方法改变了 3D 应用领域。 这项非凡的技术可以在 GitHub 上免费获取&#xff0c;允许用户在计算机上无缝运行它&#xff0c;而无需 OpenAI API 密钥或互联网连接…

2023华数杯数学建模C题思路分析 - 母亲身心健康对婴儿成长的影响

# 1 赛题 C 题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一&#xff0c;她不仅为婴儿提供营养物质和身体保护&#xff0c; 还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况&#xff0c;如抑郁、焦虑、 压力等&#xff0c;可能会对婴儿的认知、情…