使用线程池进行任务处理
线程池
线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池的应用场景:
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
线程池示例:
- 创建固定数量的线程池,循环从任务队列中获取任务对象;
- 获取到任务对象后,执行维护任务对象中的任务接口
ThreadPool_V1.hpp
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <unistd.h>
#include <pthread.h>
#include "Task.hpp"const static int N = 5;template<class T>
class ThreadPool{
public:ThreadPool(int num = N) : _num(num), _threads(num){pthread_mutex_init(&_lock, nullptr);pthread_cond_init(&_cond, nullptr);}void lockQueue(){pthread_mutex_lock(&_lock);}void unlockQueue(){pthread_mutex_unlock(&_lock);}void threadWait(){pthread_cond_wait(&_cond, &_lock); // 阻塞等待一个条件变量}void threadWakeup(){pthread_cond_signal(&_cond); // 唤醒至少一个阻塞在条件变量上的线程}bool isEmpty(){return _tasks.empty();}T popTask(){T t = _tasks.front();_tasks.pop();return t;}static void* threadRoutine(void* args){ // 静态的方法无法使用类内的成员// 1. 检测有没有任务// 2. 有:处理// 3. 无:等待// 细节:必定加锁pthread_detach(pthread_self());ThreadPool<T> *tp = static_cast<ThreadPool<T>*>(args);while (true){tp->lockQueue();while (tp->isEmpty()){// 等待,condtp->threadWait();}T t = tp->popTask(); // 将数据从公共区域拿到私有区域tp->unlockQueue();t(); // 处理任务不应该在临界区中std::cout << "thread handler done, result: " << t.formatRes() << std::endl;}}void start(){for (int i = 0; i < _num; i++){pthread_create(&_threads[i], nullptr, threadRoutine, this);}}void pushTask(const T& in){lockQueue();_tasks.push(in);threadWakeup();unlockQueue();} ~ThreadPool(){pthread_mutex_destroy(&_lock);pthread_cond_destroy(&_cond);}private:std::vector<pthread_t> _threads;int _num;std::queue<T> _tasks; // 使用stl的自动扩容的特性pthread_mutex_t _lock;pthread_cond_t _cond;
};
在V1版本中我们使用的是Linux中的线程库,代码主要是编写的是手动充当生产者,读取数据构建成为任务,然后推送给线程池,从线程池中获取任务,多线程分配任务以及并行处理。在这个线程池类中首先将加锁解锁以及条件变量相关的函数进行封装,对条件变量和互斥锁进行初始化设置,然后是创建线程用于处理任务,在此处由于类中的方法都有着隐藏的指针this因此需要将线程运行函数声明为静态函数,进程执行函数的时候首先判断是否有任务存在于任务队列中,没有的话就进行等待,若是有任务就将任务拿到自己的执行流中进行处理。
ThreadPool_V2.hpp
在这个版本中,我们将线程和互斥锁进行封装。将线程进行封装,使用vector来对线程进行管理
class Thread{
public:typedef enum{NEW = 0,RUNNING,EXITED} ThreadStatus;typedef void (*func_t)(void *);public:Thread(int num, func_t func, void *args) : _tid(0), _status(NEW), _func(func), _args(args){char name[128];snprintf(name, sizeof(name), "thread-%d", num);_name = name;}int status() { return _status; }std::string threadname() { return _name; }pthread_t threadid(){if (_status == RUNNING)return _tid;else{return 0;}}static void *runHelper(void *args){Thread *ts = (Thread*)args; // 就拿到了当前对象// _func(_args);(*ts)();return nullptr;}void operator ()() { //仿函数if(_func != nullptr) _func(_args);}void run(){int n = pthread_create(&_tid, nullptr, runHelper, this);if(n != 0) exit(1);_status = RUNNING;}void join(){int n = pthread_join(_tid, nullptr);if(n != 0){std::cerr << "main thread join thread " << _name << " error" << std::endl;return;}_status = EXITED;}~Thread() {}
private:pthread_t _tid;std::string _name;func_t _func; // 线程未来要执行的回调void *_args;ThreadStatus _status;
};
下面就是使用封装的线程类来进行线程池的代码编写
// ...
template <class T>
class ThreadPool
{
public:// ...pthread_mutex_t* getLock(){return &_lock;}static void threadRoutine(void *args){// pthread_detach(pthread_self());ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);while (true){T t;{ // 将加锁使用域进行隔离,能够进行自动的析构LockGuard lockguard(tp->getLock()); // 填入的是锁的地址while (tp->isEmpty()){tp->threadWait();}t = tp->popTask(); // 从公共区域拿到私有区域}// for testt();std::cout << "thread handler done, result: " << t.formatRes() << std::endl;}}void init(){for (int i = 0; i < _num; i++){_threads.push_back(Thread(i, threadRoutine, this));}}void start(){for (auto &t : _threads){t.run();}}void check(){for (auto &t : _threads){std::cout << t.threadname() << " running..." << std::endl;}}void pushTask(const T &t){LockGuard lockguard(&_lock);_tasks.push(t);threadWakeup();}~ThreadPool(){for (auto &t : _threads){t.join();}pthread_mutex_destroy(&_lock);pthread_cond_destroy(&_cond);}
};
// main.cc
#include <memory>
// #include "ThreadPool_V1.hpp"
#include "ThreadPool_V2.hpp"
int main(){std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>(5));tp->init();tp->start();tp->check(); while (true){int x, y;char op;std::cout << "please Enter x> ";std::cin >> x;std::cout << "please Enter y> ";std::cin >> y;std::cout << "please Enter op(+-*/%)> ";std::cin >> op;Task t(x, y, op);// 充当生产者, 从网络中读取数据,构建成为任务,推送给线程池sleep(1);tp->pushTask(t);}
}
ThreadPool_V3.hpp
最后,在这个版本中添加了单例模式中的懒汉模式,使用懒汉模式来构建线程池。
class ThreadPool
{// ...static ThreadPool<T> *getinstance(){if(nullptr == instance){ // 为什么要这样?提高效率,减少加锁的次数! LockGuard lockguard(&instance_lock);if (nullptr == instance){std::cout << "线程池单例形成" << std::endl;instance = new ThreadPool<T>();instance->init();instance->start();}}return instance;}static ThreadPool<T> *instance;static pthread_mutex_t instance_lock;
}
template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::instance_lock = PTHREAD_MUTEX_INITIALIZER;
懒汉模式需要我们有一个获取实例的函数,如果没有该实例就需要先创造出一个实例,然而这个函数并不是可重入函数,因此需要添加互斥锁保护,这时又有一个问题就是,当第一次获取了实例之后,后面就不需要再次加锁获取,为了避免每次都加锁,这里使用了双指针的形式提高效率减少加锁的次数。
相关文章:
使用线程池进行任务处理
线程池 线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分…...

ES6之Map和Set有什么不同?
一、Map 1.定义 Map是ES6提供的一种新的数据结构,它是键值对的集合,类似于对象,但是键的范围不限于字符串,各种类型的值都可以当做键。 Object结构是“字符串-值”的对应,Map结构则是“值-值”的对应 2.代码示例 M…...
Java中的集合
Java中的集合分为单列集合和双列集合,单列集合顶级接口为Collection,双列集合顶级接口为Map。 Collection 的子接口有两个:List和Set。 List 接口的特点:元素可重复,有序(存取顺序)。 List 接…...

9.4.2servlet基础2
一.SmartTomcat 1.第一次使用需要进行配置. 二.异常处理 1.404:浏览器访问的资源,在服务器上不存在. a.检查请求的路径和服务器配置的是否一致(大小写,空格,标点符号). b. 确认webapp是否被正确加载(检查web.xml没有/目录错误/内容错误/名字拼写错误)(多多关注日志信息). 2…...

嵌入式学习 - 用电控制电
目录 前言: 1、继电器 2、二极管 3、三极管 3.1 特殊的三极管-mos管 3.2 npn类型三极管 3.3 pnp类型三极管 3.4 三极管的放大特性 3.5 mos管和三极管的区别 前言: 计算机的工作的核心原理:用电去控制电。 所有的电子元件都有数据手册…...
QCA组态如何科学命名?
前言 (一)文献来源 文献来源:[1]Furnari S, Crilly D, Misangyi V F, et al. Capturing causal complexity: Heuristics for configurational theorizing[J]. Academy of Management Review, 2021, 46(4): 778-799. (二ÿ…...

外贸行业中常用的邮箱推荐
随着全球贸易的不断发展,外贸行业越来越重要。在这个过程中,电子邮件作为一种重要的沟通工具,扮演着关键的角色。然而,对于许多外贸从业者来说,选择合适的邮箱服务并不容易。本文将探讨外贸邮箱和普通邮箱的区别&#…...
高性能实践
1、认识性能 从用户体验来看,性能就是响应时间短; 从开发角度来看,性能主要是执行效率高。 性能主要表现形式如下: (1)响应时间,AVG、MAX、MIN、TP95、TP99 (2)吞吐…...

说说hashCode() 和 equals() 之间的关系?
每天一道面试题,陪你突击金九银十! 上一篇关于介绍Object类下的几种方法时面试题时,提到equals()和hashCode()方法可能引出关于“hashCode() 和 equals() 之间的关系?”的面试题,本篇来解析一下这道基础面试题。 先祭一…...
算法通关村-----图的基本算法
图的实现方式 邻接矩阵 定义 邻接矩阵是一个二维数组,其中的元素表示图中节点之间的关系。通常,如果节点 i 与节点 j 之间有边(无向图)或者从节点 i 到节点 j 有边(有向图),则矩阵中的元素值…...

基于随机森林+小型智能健康推荐助手(心脏病+慢性肾病健康预测+药物推荐)——机器学习算法应用(含Python工程源码)+数据集(二)
目录 前言总体设计运行环境Python环境依赖库 模块实现1. 疾病预测2. 药物推荐1)数据预处理2)模型训练及应用3)模型应用 其它相关博客工程源代码下载其它资料下载 前言 本项目基于Kaggle上公开的数据集,旨在对心脏病和慢性肾病进行…...
stm32学习-芯片系列/选型
【03】STM32HAL库开发-初识STM32 | STM概念、芯片分类、命名规则、选型 | STM32原理图设计、看数据手册、最小系统的组成 、STM32IO分配_小浪宝宝的博客-CSDN博客 STM32:ST是意法半导体,M是MCU/MPU,32是32位。 ST累计推出了:…...
LeetCode //C - 200. Number of Islands
200. Number of Islands Given an m x n 2D binary grid grid which represents a map of *‘1’*s (land) and *‘0’*s (water), return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically…...

使用Python构建强大的网络爬虫
介绍 网络爬虫是从网站收集数据的强大技术,而Python是这项任务中最流行的语言之一。然而,构建一个强大的网络爬虫不仅仅涉及到获取网页并解析其HTML。在本文中,我们将为您介绍创建一个网络爬虫的过程,这个爬虫不仅可以获取和保存网…...

图像处理之《基于语义对象轮廓自动生成的生成隐写术》论文精读
一、相关知识 首先我们需要了解传统隐写和生成式隐写的基本过程和区别。传统隐写需要选定一幅封面图像,然后使用某种隐写算法比如LSB、PVD、DCT等对像素进行修改将秘密嵌入到封面图像中得到含密图像,通过信道传输后再利用算法的逆过程提出秘密信息。而生…...
Java 字节流
一、输入输出流 输入输出 ------- 读写文件 输入 ------- 从文件中获取数据到自己的程序中,接收处理【读】 输出 ------- 将自己程序中处理好的数据保存到文件中【写】 流 ------- 数据移动的轨迹 二、流的分类 按照数据的移动轨迹分为:输入流 输出流…...

华硕电脑怎么录屏?分享实用录制经验!
“华硕电脑怎么录屏呀,刚买的笔记本电脑,是华硕的,自我感觉挺好用的,但是不知道怎么录屏,最近刚好要录一个教程,怎么都找不到在哪里录制,有人能教教我吗?” 随着电脑技术的不断发展…...
python学习--python的异常处理机制
try…except try:n1int(input(请输入一个整数))n2int(input(请输入另一个整数))resultn1/n2print(结果为,result) except ZeroDivisionError: print(除数不能为0)try…except…else 如果try块中没有抛出异常,则执行else块,如果try中抛出异常࿰…...

nacos+Dubbo整合快速入门
官网:Nacos Spring Boot 快速开始 下载下载链接启动:进入bin目录,startup.cmd -m standalone引入依赖 <dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo</artifactId><version>3.0.9…...

QT实现钟表
1、 头文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QPaintEvent> //绘制事件类 #include <QDebug> //信息调试类 #include <QPainter> //画家类 #include <QTimerEve…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...

C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...