线程池小项目【Linux C/C++】(踩坑分享)

目录

前提知识:

一,线程池意义

二,实现流程

阶段一,搭建基本框架

1. 利用linux第三方库,将pthread_creat线程接口封装

2. 实现基本主类ThreadPool基本结构 

阶段二,完善多线程安全

1. 日志信息打印——模拟企业级日常日志记录

2. C/C++的格式化输出

3. C,C++接口套用时,考虑this指针

阶段三,优化为单例模式——懒汉

四,源码


嗨!收到一张超美的风景图,愿你每天都能顺心

前提知识:

C/C++线程接口使用,可参考多线程基础入门【Linux之旅】——上篇【线程控制 || 线程互斥 || 线程安全】-CSDN博客

互斥锁,信号量知识,可参考多线程基础入门【Linux之旅】——下篇【死锁 || 条件变量 || 生产消费者模型 || 信号量】-CSDN博客

C++STL接口使用,如:queue,string等

单例模式,懒汉与饿汉模式,可参考C++特殊类设计【特殊类 || 单例对象 || 饿汉模式 || 懒汉模式】-CSDN博客

一,线程池意义

   一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内 存、网络sockets 等的数量。
线程池的应用场景:
1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB 服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet 连接请求,线程池的优点就不明显了。因为 Telnet 会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
线程池示例:
  1. 创建固定数量线程池,循环从任务队列中获取任务对象。
  2. 获取到任务对象后,执行任务对象中的任务接口。

二,实现流程

阶段一,搭建基本框架

 目标:

1. 利用linux第三方库,将pthread_creat线程接口封装

#include <iostream>
#include <pthread.h>
#include <functional>
#include <string>
#include <unistd.h>typedef void* (*func_t)(void*); // pthread_creat() C接口不接受包装器functional// 当线程处理函数运行时需要便捷得知当前线程的信息,
// 因此我们将传入一个结构体。
class ThreadDate
{
public:void* args;         // 线程任务函数参数std::string _name;  // 线程名字pthread_t _id;      // 线程ID// void* _TP;       // 主进程对象
};class Thread
{
public:Thread(int pid, func_t calltalk, void* args):func_(calltalk){char name[64]; snprintf(name, sizeof name, "Thread - %d ", pid);  // 将线程编号导入到名字中trda_._name = name; trda_._id = pid;}void start(){pthread_create(&trda_._id, nullptr, func_ , (void*)&trda_);}void join(){pthread_join(trda_._id ,nullptr);}const std::string& name(){return trda_._name;}private:func_t func_;       // 处理函数ThreadDate trda_; // 给任务提供的线程等其他信息
};

当然 我们也可以直接使用C++11提供的thread库,这会简单许多。

2. 实现基本主类ThreadPool基本结构 

template <class T>
class ThreadPool
{
public:ThreadPool(int num = THREADPOOL_NUM):_pnum(num){for (int i = 0; i < num; i++){_thr_pool.push_back(new Thread(i + 1, routine, nullptr));}}// 多线程处理函数——重要函数static void* routine(void* trda)  // 关于 routine函数,要设置为静态的原因:// 在Thread构造中,第二位为void*(*func_t)(void*)类型// 而类中成员函数都隐藏了一个this指针,这样会导致routine类型不匹配的问题{ThreadDate* _trda = static_cast<ThreadDate*>(trda);std::cout <<  _trda->_name << std::endl;return nullptr;}// 线程池维护区void Run(){for (auto& e : _thr_pool){e->start();}}~ThreadPool(){   for (auto& e : _thr_pool){e->join();delete(e);}}private:std::vector<Thread*> _thr_pool;int _pnum;  // 有效线程数
};#endif

阶段二,完善多线程安全

目标:对各线程访问任务队列进行加锁以及信号量保护。
    static void* routine(void* trda)  // 关于 routine函数,要设置为静态的原因:// 在Thread构造中,第二位为void*(*func_t)(void*)类型// 而类中成员函数都隐藏了一个this指针,这样会导致routine类型不匹配的问题{ThreadDate* _trda = static_cast<ThreadDate*>(trda);ThreadPool<T>* th = static_cast<ThreadPool<T>*>(_trda->args); T task;while (1)  //不断获取任务{{LockGuard lk(th->get_mutex()); //自己封装的一个加锁类while (th->get_queue_empty()) {th->wait_cond();}task = th->get_task();  //获取任务,执行...}(*task)(_trda->_name);sleep(1);}return nullptr;}

小知识积累:

1. 日志信息打印——模拟企业级日常日志记录

下面是比较标准的日志模式打印,我们可以利用条件编译的方式选择日志输出格式(终端,文件),(不过一般还好全编译为好,除非两者互斥) 

// C语言形式实现一个比较标准的日志格式
// 日志级别
#define NOWAIN 1 // 正常日志
#define DEBUG 2  // debug日志
#define WAIN 3   // 警告但能运行
#define ERROR 4  // 错误但能运行
#define FATIL 0  // 致命错误,运行停止const char* GetlevelMap[] = {
"FATIL",
"NOWAIN",
"DEBUG",
"WAIN",
"ERROR"
};//公司常见的日志信息:级别,时间,标准内容(文件名代码位置) + 用户自定义内容
void Logmessage(int level, const char* format , ...)
{
#ifdef REALSE  // 默认DEBUG// 标准日志char normal[1024];time_t tm = time(0);  // 获取时间戳snprintf(normal, sizeof normal, "[%s] time[%d]", GetlevelMap[level], tm);printf("%s\n", normal);  
#else// 标准日志char normal[1024];time_t tm = time(0);  // 获取时间戳snprintf(normal, sizeof normal, "[%s] time[%d]", GetlevelMap[level], tm);// 自定义日志——DEBUG阶段char custom_log[1024];va_list v_li; // 本质是 char*va_start(v_li, format); // 设置成format这样将打印全部自定义内容vsnprintf(custom_log, sizeof custom_log, format, v_li);printf("%s %s\n", normal, custom_log);va_end(v_li);
#endif
}

2. C/C++的格式化输出

 关于C语言,C++的格式化输出,在格式化输出方面,C做的相对较好,C++就只有个cout。下面是C格式输出的一些常用接口:

3. C,C++接口套用时,考虑this指针

主要是C接口调用C++类中成员函数出的问题,因为类成员函数参数里面隐藏了this指针。

阶段三,优化为单例模式——懒汉

关于 懒汉模式详细信息可看本文开头的链接
    ......// 通过该指令加载,使用时才进行加载——懒汉模式static ThreadPool<T>* Get_Instance(int num = THREADPOOL_NUM){if (st == nullptr){mutex_inital.lock();if (st == nullptr){st =  new ThreadPool<T>(num);}mutex_inital.unlock();}return st;}............int Get_queue_task_size(){return task_->size();}int Get_queue_task_reserver_size(){return task_reserver->size();}// 1.禁用拷贝&赋值ThreadPool<T>(const ThreadPool<T>& it) = delete;ThreadPool<T>& operator= (const ThreadPool<T>& it) = delete;private:// 2.构造函数私有ThreadPool(int num = THREADPOOL_NUM):_pnum(num), task_(new std::queue<T>),task_reserver(new std::queue<T>){for (int i = 0; i < num; i++){_thr_pool.push_back(new Thread(i + 1, routine, this));// 考虑到处理方法可能还会获取进程池的信息,因此我们将进程池传入}pthread_mutex_init(&mtx_, nullptr);pthread_cond_init(&cond_, nullptr);}std::vector<Thread*> _thr_pool;int _pnum;  // 有效线程数std::queue<T>* task_;std::queue<T>* task_reserver;pthread_mutex_t mtx_;pthread_cond_t cond_; // 估计就是设置的资源量static ThreadPool<T>* st;static std::mutex mutex_inital; //这里直接用C++的锁,防止覆盖锁
};template <class T>
ThreadPool<T>* ThreadPool<T>::st = nullptr;
template <class T>
std::mutex ThreadPool<T>::mutex_inital;

debug踩坑分享

1. 调试,不如直接打印强

2. 关于任务存放的问题,首先让我们看看上面程序的测试代码,testmain

int main()
{ThreadPool<Task_add*>* st = ThreadPool<Task_add*>::Get_Instance(); //加载st->Run();  //线程池启动// 主函数就负责生产任务,向任务队列输入任务// 未来可能是网络端获取任务srand(time(0) * 131 + 1);while (1){   if (st->Get_queue_task_reserver_size() < QUEUE_TASK_NUM){int x = rand() / 20 + 1;usleep(7777);   int y = rand() / 30 + 4;Task_add tk (x, y,  [](int a, int b){ return a + b;});st->push(&tk);}// 备用队列超过5个任务再交换队列if (st->Get_queue_task_reserver_size() >= QUEUE_TASK_NUM / 2  &&st->Get_queue_task_size() == 0 &&st->Get_queue_task_reserver_size() <= QUEUE_TASK_NUM){st->swap_queue();}}

不卖关子了,问题出在tk变量,我选择储存在栈上,同时循环的使用tk来创建任务,但任务队列导入的是tk地址,这就导致线程都是获取第5个任务的相同数据,解决方法:改成堆上储存,然后手动释放,这样基本上就不会出现数据覆盖的问题。

四,源码

源码: ThreadPool · 逆光/Linux - 码云 - 开源中国 (gitee.com)

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

若依框架时间比较的坑(DATE_FORMAT)

背景 - 想做生日的比较 若依自带的比较 <if test"params.beginTime ! null and params.beginTime ! "><!-- 开始时间检索 -->AND date_format(u.create_time,%y%m%d) > date_format(#{params.beginTime},%y%m%d)</if><if test"params…

AJAX —— 学习(三)

目录 一、jQuery 中的 AJAX &#xff08;一&#xff09;get 方法 1.语法介绍 2.结果实现 &#xff08;二&#xff09;post 方法 1.语法介绍 2.结果实现 &#xff08;三&#xff09;通用型的 AJAX 方法 1.语法介绍 2.结果实现 二、AJAX 工具库 axios &#xff08;…

【进阶六】Python实现SDVRPTW常见求解算法——遗传算法(GA)

基于python语言&#xff0c;采用经典遗传算法&#xff08;GA&#xff09;对 带硬时间窗的需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时长取值问题 3. 求解结果4. 代码片段参…

【Qt】Ubuntu20.04.6+Qt5.15.2+QtCreator10.0.1无法输入中文

1、前提条件 1)已经安装了fcitx sudo apt install fcitx sudo apt install fcitx-pinyin sudo apt install fcitx-bin fcitx-table-all sudo apt install fcitx-qt52)系统已经配置fcitx 3)将系统下 /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitx…

计算机考研408有向无环图描述表达式可靠构造方法

目录 前言目标&#xff08;以王道书为例&#xff09;构造方法1. 建树2. 后序遍历1. a2. b3. 4. b5. c6. d7. 8. *9. *10. c 前言 对王道视频中的分层合并思想不是很满意&#xff0c;笔者提出自己的构造方法。 目标&#xff08;以王道书为例&#xff09; 构造方法 笔者通过王…

Doris实践——同程数科实时数仓建设

目录 前言 一、早期架构演进 二、Doris和Clickhouse选型对比 三、新一代统一实时数据仓库 四、基于Doris的一站式数据平台 4.1 一键生成任务脚本提升任务开发效率 4.2 自动调度监控保障任务正常运行 4.3 安全便捷的可视化查询分析 4.4 完备智能的集群监控 五、收益与…

线控悬架系统分析

线控悬架系统分析 附赠自动驾驶学习资料和量产经验&#xff1a;链接 1 线控悬架系统系统发展现状 • 车辆驾乘过程中&#xff0c;操控性和舒适性是两个重要的评价指标&#xff0c;两者很难兼顾&#xff1b; • 线控悬架就是根据路况实际情况自动调节悬架的高度、刚度、阻尼实…

012_control_flow_in_Matlab中的控制流

Matlab中的控制流 虽然&#xff0c;我们说Matlab中的计算是向量化的&#xff0c;但是在某些情况下&#xff0c;作为一个“程序设计语言”&#xff0c;Matlab也提供了一些控制流结构&#xff0c;来帮助我们实现一些复杂的逻辑。 我会在介绍控制流的时候&#xff0c;提醒如何用…

Ansys Zemax | 如何将光栅数据从Lumerical导入至OpticStudio(上)

附件下载 联系工作人员获取附件 本文介绍了一种使用Ansys Zemax OpticStudio和Lumerical RCWA在整个光学系统中精确仿真1D/2D光栅的静态工作流程。将首先简要介绍方法。然后解释有关如何建立系统的详细信息。 本篇内容将分为上下两部分&#xff0c;上部将首先简要介绍方法工…

【Leetcode】top 100 技巧

136 只出现一次的数字 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 技巧&#xff1a…

LeetCode 96. 不同的二叉搜索树

给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;1提…

代码随想录算法训练营第二十二天| 235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

系列文章目录 目录 系列文章目录235. 二叉搜索树的最近公共祖先①递归法自己写的简洁版 ②迭代法不能这样写&#xff01;正确写法 701.二叉搜索树中的插入操作①递归法②迭代法 450.删除二叉搜索树中的节点递归法 235. 二叉搜索树的最近公共祖先 ①递归法 自己写的 class So…

书生·浦语大模型InternLM-Chat-1.8B 智能对话 Demo 第二期

文章目录 InternLM-Chat-1.8B 智能对话 Demo环境准备下载模型运行 InternLM-Chat-1.8B web 运行八戒 demo下载模型执行Demo InternLM-Chat-1.8B 智能对话 Demo 环境准备 在InternStudio平台中选择 10% A100(1/4) 的配置&#xff08;平台资源有限&#xff09;&#xff0c;如下图…

【c语言】自定义类型:联合体(公用体)【详解】

联合体 联合体类型的声明 像结构体⼀样&#xff0c;联合体也是由⼀个或者多个成员构成&#xff0c;这些成员可以不同的类型。但是编译器只为最⼤的成员分配⾜够的内存空间。联合体的特点是所有成员共⽤同⼀块内存空间。所以联合体也叫&#xff1a;共用体。 给联合体其中⼀个成…

2024阿里云域名优惠口令免费领取,COM、CN和xin域名口令

2024年阿里云域名优惠口令&#xff0c;com域名续费优惠口令“com批量注册更享优惠”&#xff0c;cn域名续费优惠口令“cn注册多个价格更优”&#xff0c;cn域名注册优惠口令“互联网上的中国标识”&#xff0c;阿里云优惠口令是域名专属的优惠码&#xff0c;可用于域名注册、续…

【QT入门】 自定义标题栏界面qss美化+按钮功能实现

往期回顾&#xff1a; 【QT入门】 鼠标按下和移动事件实现无边框窗口拖动-CSDN博客【QT入门】 设计实现无边框窗口拉伸的公用类-CSDN博客【QT入门】对无边框窗口自定义标题栏并实现拖动和拉伸效果-CSDN博客 【QT入门】 自定义标题栏界面qss美化按钮功能实现 一、最终效果 二、…

【JAVASE】学习类与对象的创建和实例化

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;再无B&#xff5e;U&#xff5e;G-CSDN博客 目标&#xff1a; 1. 掌握类的定义方式以及对象的实例化 2. …

MVCC的实现原理

简介 MVCC&#xff08;Multi-Version Concurrency Control&#xff09;即多版本并发控制。 MVCC的实现原理 我们在了解MVCC之前&#xff0c;首先先了解一下几个比较常见的锁。 **读锁&#xff1a;**也叫共享锁、S锁&#xff0c;若事务T对数据对象A加上S锁&#xff0c;则事务…

一维卷积神经网络的特征可视化

随着以深度学习为代表的人工智能技术的不断发展&#xff0c;许多具有重要意义的深度学习模型和算法被开发出来&#xff0c;应用于计算机视觉、自然语言处理、语音处理、生物医疗、金融应用等众多行业领域。深度学习先进的数据挖掘、训练和分析能力来源于深度神经网络的海量模型…

使用OpenCV4.9的随机生成器和文本

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV 4.9基本绘图 下一篇&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用随机数生…