【Linux】线程池实现
📗线程池实现(单例模式)
- 1️⃣线程池概念
- 2️⃣线程池代码样例
- 3️⃣部分问题与细节
- 🔸类成员函数参数列表中隐含的this指针
- 🔸单例模式
- 🔸一个失误导致的bug
- 4️⃣调用线程池完成任务
1️⃣线程池概念
线程池是一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池的应用场景:
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
线程池示例:
- 创建固定数量线程池,循环从任务队列中获取任务对象,
- 获取到任务对象后,执行任务对象中的任务接口
2️⃣线程池代码样例
以下为线程池代码:
#pragma once#include <iostream>
#include <queue>
#include <pthread.h>
#include <ctime>template<class T>
class ThreadPool
{private:std::queue<T> _q;//任务队列pthread_mutex_t _lock;pthread_cond_t _cond;//有任务时提醒线程执行任务static ThreadPool<T>* _instance;ThreadPool(){} public:static ThreadPool<T>* getInstance()//单例模式,饿汉模式,静态成员{static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;if(nullptr == _instance)//双if提高效率,因为只有第一次获取_instance时才会实例化,后续无需再加锁实例化_instance{pthread_mutex_lock(&mtx);if(nullptr == _instance){_instance = new ThreadPool<T>();}pthread_mutex_unlock(&mtx);}return _instance;}void MutexInit(){pthread_mutex_init(&_lock, nullptr);}void MutexLock(){pthread_mutex_lock(&_lock);}void MutexUnLock(){pthread_mutex_unlock(&_lock);}bool IsEmpty(){return _q.size() == 0 ? true : false;}void ThreadWait(){pthread_cond_wait(&_cond, &_lock);}void ThreadWakeUp(){pthread_cond_signal(&_cond);}void PopTask(T* out)//取任务{//此处不应加锁,应为取任务的时候是带着锁的,若此时申请锁,会出现死锁现象*out = _q.front();_q.pop();}//类内部的成员方法都有隐含的this参数,因此要加上static修饰static void* Routine(void* args){ThreadPool<T>* tp = (ThreadPool<T>*) args;//接收this指针pthread_detach(pthread_self());//线程分离while(true){tp->MutexLock();//加锁,访问临界区_qwhile(tp->IsEmpty())//任务队列为空,挂起等待{tp->ThreadWait();}//到此处说明有任务T t;tp->PopTask(&t);tp->MutexUnLock();//退出临界区_qt();}return nullptr;}void ThreadPoolInit(int num)//初始化线程池{pthread_t p[num];for(int i = 0; i < num; i++){pthread_create(p + i, nullptr, Routine, this);//将this指针作为参数传入}}void PushTask(const T& in){//分配任务MutexLock();_q.push(in);MutexUnLock();ThreadWakeUp();//唤醒线程完成任务}~ThreadPool(){pthread_mutex_destroy(&_lock);}
};template<class T>
ThreadPool<T>* ThreadPool<T>::_instance = nullptr;
3️⃣部分问题与细节
下面分享一些在编写该单例模式线程池代码遇到的一些问题与细节:
🔸类成员函数参数列表中隐含的this指针
我们在初始化线程池的这部分代码,需要创建若干线程来完成其所需要执行的任务,这些线程的例程函数形式为void *(*start_routine) (void *) ,其参数列表中仅有一个参数void*,而如果将这个例程函数定义成成员函数,会有一个隐含的this指针参数,导致形式不一致,因此需要将该例程函数用static修饰为静态的。
又因为静态成员函数只能访问静态成员变量,故我们需要在创建线程时将this指针通过参数传递给例程函数,这样才能在例程函数中使用this指针访问类中的成员变量。

🔸单例模式
我们这个线程池设计成了单例模式,并且采用的是饿汉模式,即服务启动后只有在用到线程池这个功能时才会创建对象。而在单例模式创建对象时,由于只有第一次创建对象时对象指针为nullptr,故判断是否要创建对象指针的时候可以在加锁之前再进行一次判断提高效率,而无需每次都要先加锁再判断。

🔸一个失误导致的bug
在线程取任务的接口设计时,我因为这里需要访问任务队列这个临界区给这个过程加上了锁,但是实际上在调用这个接口的时候其实线程就已经申请加了锁,而且两次申请的为同一把锁,就导致出现了线程在已经持有一把锁的情况下又去申请这把锁,从而产生了死锁。

4️⃣调用线程池完成任务
任务类:
实现x 与 y 的+ - * / % 五种运算。
#pragma once
#include <iostream>class Task//x op y = z
{private:int x;int y;char op;//+-*/%public:Task(){}Task(int _x, int _y, char _op):x(_x),y(_y),op(_op){}void operator()(){int z = -1;switch (op){case /* constant-expression */'+':/* code */z = x + y;break;case '-':z = x - y;break;case '*':z = x * y;break;case '/':if(0 != y)z = x / y;elsestd::cout << "warning: div zero error" << std::endl;break;case '%':if(0 != y)z = x % y;elsestd::cout << "warning: div zero error" << std::endl;break;default:std::cout << "unkonwn operator" << std::endl;break;}std::cout << "[" << pthread_self() << "] handler task: " << x << " " << op << " " << y << " = " << z << std::endl;}~Task(){}
};
主函数:
#include "thread_pool.hpp"
#include "Task.hpp"
#include <string>
#include <unistd.h>int main()
{srand((unsigned long)time(nullptr));ThreadPool<Task>* tp = ThreadPool<Task>::getInstance();tp->ThreadPoolInit(5);const std::string s = "+-*/%";while(true){int x = rand() % 50;int y = rand() % 50;char op = s[rand() % 5];Task t(x, y, op);tp->PushTask(t);sleep(1);}return 0;
}

结果如上,左侧为线程池中的线程每隔一秒取出任务并执行,右侧为线程池的情况。
相关文章:
【Linux】线程池实现
📗线程池实现(单例模式) 1️⃣线程池概念2️⃣线程池代码样例3️⃣部分问题与细节🔸类成员函数参数列表中隐含的this指针🔸单例模式🔸一个失误导致的bug 4️⃣调用线程池完成任务 1️⃣线程池概念 线程池是…...
使用Python批量上传本地maven库到nexus
背景:外包类项目开发时是调用的公司maven仓库进行开发,交付后需要将maven仓库转移到客户环境。 原理:1、打开idea运行源代码,将maven包下载到本地仓库, 2、下载包所在目录中执行脚本将本地仓库的maven包上传到客户nex…...
【Unity实战100例】Unity对Ini格式的配置文件管理和读写
目录 一.编写ini格式配置文件 二.读取解析ini文件 三.调用属性 INI 文件以文本形式存储,易于阅读和编辑。这种人可读的格式使得调整配置参数变得更加直观,不需要专门的工具。 INI 文件是一种轻量级的配置文件格式,不需要复杂的解析器或库。它的结构相对简单,适用于小到...
k8s存储卷和数据卷下
静态pv和pvc 运维负责pv:创建号持久化存储卷,申明好读写和挂载类型,以及可以提供的存储空间 Pvc开发做,要和开发沟通好,你期望的读写和挂载类型,以及存储空间 当我发布vc之后可以生成pv,还可以在…...
SQL Server 配置远程连接
Windows 安装好 SQL Server 的 SSMS,打开SSMS配置远程连接 找到 配置管理器 启用 TCP/IP 打开防火墙设置 新建入站规则 端口TCP - 特定本地端口 (1433)允许连接下一步名称完成 重启 SQL Server 服务...
vscode(visual studio code) 免密登陆服务器
1.生成密钥 首先,在本地,打开命令输入框: WinR–>弹出输入框,输入cmd,打开命令框。 然后,在命令框,输入 ssh-keygen -t rsa -C "love"按两次回车键,问你是否重写,选择…...
[redis] redis主从复制,哨兵模式和集群
一、redis的高可用 1.1 redis高可用的概念 在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。 高可用的计算公式是1-(宕机时间)/(宕机时…...
debian12部署Gitea服务
首先安装git、wget、sqlite,然后进行用户和组的相关设置 sudo apt install -y git wget sqlite3 新增一个git用户与一个git组 sudo adduser --system --group --disabled-password --shell /bin/bash --home /home/git --gecos Git Version Control git 给git用户设…...
js动态设置关键侦@keyframes
js动态设置关键侦keyframes 1.前置知识 关键侦keyframes规则通过在动画序列中定义关键侦的样式来控制CSS动画序列的中间步骤 keyframes slidein {from {transform: translateX(0%);}to {transform: translateX(100%);} } // from 等价于 0%;to 等价与 100% // 或…...
【WPF.NET开发】流文档
本文内容 什么是流文档?流文档类型创建流内容与流相关的类内容架构自定义文本 流文档旨在优化查看和可读性。 流文档根据运行时变量(例如,窗口大小、设备分辨率和可选的用户首选项)来动态调整和重新排列内容,而不是设…...
golang学习-结构体
1、定义 使用type 和struct 关键字来定义结构体,是值类型 格式如下: type 类型名 struct { 字段名 类型 字段名 类型 ... } 2、实例化 1、var 结构体实例 结构体类型 var p1 Person 2、使用new关键字 var p2 new(Person) 3、使用&对结构体…...
Python:enumerate() 函数
enumerate() 函数用于同时遍历索引和元素,常用于循环中。这个函数返回一个包含索引和元素的元组,可以通过解包的方式获取它们。 使用方法: enumerate(iterable, start0). iterable: 要遍历的可迭代对象。start: 索引起始值,默认…...
FPGA 移位运算与乘法
题目: 已知d为一个8位数,请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输入的d有效(d给出的信号的上升沿表示写入有效) 由题意可知: 复位信号高有效,低复位;在inpu_grant上升…...
网络安全B模块(笔记详解)- MYSQL信息收集
MYSQL信息收集 1.通过渗透机场景Kali中的渗透测试工具对服务器场景MySQL03进行服务信息扫描渗透测试(使用工具Nmap,使用必须要使用的参数),并将该操作显示结果中数据库版本信息作为Flag提交; Flag:MySQL 5.5.12 2.通过渗透机场景Kali中的渗透测试工具对服务器场景MySQL0…...
从JavaScript的角度上讲解一下xml
- XML(可扩展标记语言) XML(可扩展标记语言)是一种被设计用于存储和传输结构化数据的标记语言。它与HTML相似,但XML并没有预定义的标签,可以自定义标签及其属性。从JavaScript的角度来看,XML可以…...
Pandas实战100例 | 案例 13: 数据分类 - 使用 `cut` 对数值进行分箱
案例 13: 数据分类 - 使用 cut 对数值进行分箱 知识点讲解 在数据分析中,将连续的数值数据分类成不同的区间(或“分箱”)是一种常见的做法。Pandas 提供了 cut 函数,它可以根据你指定的分箱边界将数值数据分配到不同的类别中。 …...
python统计分析——操作案例(模拟抽样)
参考资料:用python动手学统计学 import numpy as np import pandas as pd from matplotlib import pyplot as plt import seaborn as snsdata_setpd.read_csv(r"C:\python统计学\3-4-1-fish_length_100000.csv")[length] #此处将文件路径改为自己的路…...
部署Tomcat及其负载均衡
Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。一般来说,Tomcat虽然和Apache或者Nginx这些Web服务器一样…...
C++ 类、结构体
C 类、结构体 类可以将变量、数组和函数完美地打包在一起。 类与结构体 类的定义: class Person {private:int age,height;double money;string books[100];public:string name;void say(){cout<<"Im"<<name<<endl;}int get_age(){…...
数据结构(三)堆和哈希表
目录 哈希表和堆什么是哈希表 ?什么是堆 ?什么是图 ?案例一:使用python实现最小堆案例二 : 如何用Python通过哈希表的方式完成商品库存管理闯关题 (包含案例三:python实现哈希表) 本…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
