【Linux】线程池详解及其基本架构与单例模式实现
目录
1.关于线程池的基本理论
1.1.线程池是什么?
1.2.线程池的应用场景:
2.线程池的基本架构
2.1.线程容器
2.2.任务队列
2.3.线程函数(HandlerTask)
2.4.线程唤醒机制
3.添加单例模式
3.1.单例模式是什么?
3.2.饿汉实现方式和懒汉实现方式
饿汉式单例模式:
懒汉式单例模式:
3.3.改写懒汉式的单例模式
双判断的方式为什么能减少单例的加锁成本呢?
单判断为什么会出错?
单例模式的注意点:
4.代码和执行效果
1.关于线程池的基本理论
1.1.线程池是什么?
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
1.2.线程池的应用场景:
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
2.线程池的基本架构
- 线程容器:用来管理创建的线程,方便统一初始化。
- 任务队列:用来储存任务消息,需要支持压入与取出的操作。
- 线程函数(HandlerTask):线程都需要执行这个函数模块,在这个函数模块中进行任务的等待和执行。
- 线程唤醒机制:需要一个线程唤醒机制,通过条件变量和互斥锁完成对线程的保护与唤醒。
- 单例模式:线程池不需要创建多个,一个程序只需要一个线程池,通过单例模式进行优化。
2.1.线程容器
我们使用vector容器来存储线程,并且使用自己封装的线程来实现线程使用的各个接口
std::vector<Thread> _threads;
2.2.任务队列
我们使用队列这个容器来存储任务,并且利用队列FIFO的特性进行存储任务和取出任务
std::queue<T> _task_queue;
2.3.线程函数(HandlerTask)
我们首先要明确线程需要死循环去执行任务,所以需要while一直循环,直到线程池已经退出了&&任务队列是空的。执行任务的同时还需要保证线程的安全,所以需要加锁来保证。
void HandlerTask(std::string name){LOG(INFO, "%s is running...", name.c_str());//线程需要死循环去处理任务while(true){//1、保证队列安全LockQueue();//2、队列中不一定有数据while(_task_queue.empty() && _isrunning){_waitnum++;ThreadSleep();_waitnum--;}//2.1 如果线程池已经退出了&&任务队列是空的if(_task_queue.empty() && !_isrunning){UnlockQueue();break;}// 2.2 如果线程池不退出 && 任务队列不是空的// 2.3 如果线程池已经退出 && 任务队列不是空的 --- 处理完所有的任务,然后在退出// 3. 一定有任务, 处理任务T t = _task_queue.front();_task_queue.pop();UnlockQueue();LOG(DEBUG, "%s get a task", name.c_str());//4.处理任务,这个任务属于线程独占的任务//t();LOG(DEBUG, "%s handler a task, result is: %s", name.c_str(), t.ResultToString().c_str());}}
2.4.线程唤醒机制
需要一个线程唤醒机制,通过条件变量加互斥锁完成对线程的保护与唤醒。
3.添加单例模式
3.1.单例模式是什么?
某些类, 只应该具有一个对象(实例), 就称之为单例。在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.
3.2.饿汉实现方式和懒汉实现方式
饿汉式单例模式:
饿汉式单例模式在类加载时就完成了实例的创建。这种方式的特点是线程安全,因为 JVM 在加载类时会对静态变量进行初始化,并且这个过程是线程互斥的。
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};
缺点:程序启动的时候,可能会很慢!所以我们一般不用饿汉
懒汉式单例模式:
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};
缺点:存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
但是后续再次调用, 就没有问题了.
其实在日常使用中,我们一般不会使用饿汉式单例模式,因为它启动的时候过慢,所以我们来改写基于懒汉式的单例模式,主要解决线程安全的问题!
3.3.改写懒汉式的单例模式
添加双判断来解决线程安全问题。
static ThreadPool<T> *GetInstance(){// 如果是多线程获取线程池对象下面的代码就有问题了!!// 只有第一次会创建对象,后续都是获取// 双判断的方式,可以有效减少获取单例的加锁成本,而且保证线程安全if (nullptr == _instance) // 保证第二次之后,所有线程,不用在加锁,直接返回_instance单例对象{LockGuard lockguard(&_lock);if (nullptr == _instance){_instance = new ThreadPool<T>();_instance->InitThreadPool();_instance->Start();LOG(DEBUG, "创建线程池单例");return _instance;}}LOG(DEBUG, "获取线程池单例");return _instance;}
双判断的方式为什么能减少单例的加锁成本呢?
我们主要解决的是害怕多线程创建不止一个单例,我们的目的是让该单例模式只生产一个单例!围绕这一个核心去解决问题!
同时有很多进程过来的时候,都会去尝试加锁,但是只有一个线程可以加锁成功,然后会执行new操作,这时候_instance == nullptr就不成立了,再后来的线程不会等待在锁上了,直接判断外层的if就会退出了,不然所有的线程都要等待锁了。
单判断为什么会出错?
同时可能多个线程通过if判断,等待锁,第一个线程加锁完成之后,执行创建,退出之后其他线程可以继续抢锁,抢到以后继续创建,就保证不了线程安全!
单例模式的注意点:
- 单例模式下的构造函数必须要有,但必须是私有的。
- 赋值和拷贝函数禁用,因为只创建1个单例
-
在类里面创建的静态变量在类内定义,需要在类外初始化
4.代码和执行效果
代码:
#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <pthread.h>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"using namespace ThreadModule;const static int gdefaultthreadnum = 5;template <typename T>
class ThreadPool
{
public:static ThreadPool<T> *GetInstance(){// 如果是多线程获取线程池对象下面的代码就有问题了!!// 只有第一次会创建对象,后续都是获取// 双判断的方式,可以有效减少获取单例的加锁成本,而且保证线程安全if (nullptr == _instance) // 保证第二次之后,所有线程,不用在加锁,直接返回_instance单例对象{LockGuard lockguard(&_lock);if (nullptr == _instance){_instance = new ThreadPool<T>();_instance->InitThreadPool();_instance->Start();LOG(DEBUG, "创建线程池单例");return _instance;}}LOG(DEBUG, "获取线程池单例");return _instance;}void Stop(){LockQueue();_isrunning = false;ThreadWakeup();UnlockQueue();}void Wait(){for(auto &thread : _threads){thread.Join();LOG(INFO, "%s is quit...", thread.name().c_str());}}bool Enqueue(const T &t){bool ret = false;LockQueue();if(_isrunning){_task_queue.push(t);if(_waitnum > 0){ThreadWakeup();}LOG(DEBUG, "enqueue task success");ret = true;}UnlockQueue();return ret;}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}private:void LockQueue(){pthread_mutex_lock(&_mutex);}void UnlockQueue(){pthread_mutex_unlock(&_mutex);}void ThreadSleep(){pthread_mutex_unlock(&_mutex);}void ThreadWakeup(){// 唤醒一个等待特定条件变量的线程pthread_cond_signal(&_cond);}void ThreadWakeupAll(){// 唤醒所有等待特定条件变量的线程pthread_cond_broadcast(&_cond);}// 单例模式下的构造函数必须要有,但必须是私有的ThreadPool(int threadnum = gdefaultthreadnum) : _threadnum(threadnum), _waitnum(0), _isrunning(false){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);LOG(INFO, "ThreadPool Construct()");}// 赋值和拷贝函数禁用,因为只创建1个单例ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;ThreadPool(const ThreadPool<T> &) = delete;void Start(){for (auto &thread : _threads){thread.Start();}}void InitThreadPool(){//构建出所有的线程,并不启动for(int num = 0; num < _threadnum; num++){std::string name = "thread" + std::to_string(num+1);//bind函数到底有什么作用???_threads.emplace_back(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1), name);LOG(INFO, "init thread %s done", name.c_str());}}void HandlerTask(std::string name){LOG(INFO, "%s is running...", name.c_str());//线程需要死循环去处理任务while(true){//1、保证队列安全LockQueue();//2、队列中不一定有数据while(_task_queue.empty() && _isrunning){_waitnum++;ThreadSleep();_waitnum--;}//2.1 如果线程池已经退出了&&任务队列是空的if(_task_queue.empty() && !_isrunning){UnlockQueue();break;}// 2.2 如果线程池不退出 && 任务队列不是空的// 2.3 如果线程池已经退出 && 任务队列不是空的 --- 处理完所有的任务,然后在退出// 3. 一定有任务, 处理任务T t = _task_queue.front();_task_queue.pop();UnlockQueue();LOG(DEBUG, "%s get a task", name.c_str());//4.处理任务,这个任务属于线程独占的任务//t();LOG(DEBUG, "%s handler a task, result is: %s", name.c_str(), t.ResultToString().c_str());}}int _threadnum;std::vector<Thread> _threads;std::queue<T> _task_queue;pthread_mutex_t _mutex;pthread_cond_t _cond;int _waitnum;bool _isrunning;// 添加单例模式static ThreadPool<T> *_instance;static pthread_mutex_t _lock;
};// 在类里面创建的静态变量在类内定义,需要在类外初始化
template <typename T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;template <typename T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;// 为什么双重判断就可以解决线程安全的问题?
// 为什么static就可以不用创建对象直接调用函数呢?
执行结果:
相关文章:

【Linux】线程池详解及其基本架构与单例模式实现
目录 1.关于线程池的基本理论 1.1.线程池是什么? 1.2.线程池的应用场景: 2.线程池的基本架构 2.1.线程容器 2.2.任务队列 2.3.线程函数(HandlerTask) 2.4.线程唤醒机制 3.添加单例模式 3.1.单例模式是什么&…...
运输层知识点汇总3
目录 前言 二、为什么在TCP首部中有一个首部长度字段,而UDP的首部就没有这个字段? 三、一个TCP报文段的数据部分最多为多少字节?为什么?如果用户要传送的数据的字节长度超过TCP报文段中的序号字段可能编出的最大序号࿰…...
浔川社团官方联合会提前入驻
近期,我们浔川社团官方发现文章再次被不明网站转发。这一情况引起了我们的高度警觉和深刻反思。我们一直致力于为大家提供高质量、有价值的内容,用心创作每一篇文章,以展现我们社团的风采和理念。然而,这些不明网站未经授权的转发…...

比例数据可视化(Python实现板块层级图绘制)——Instacart Market Basket Analysis
【实验名称】 实验一:绘制板块层级图 【实验目的】 1. 掌握数据文件读取 2. 掌握数据处理的方法 3. 实现板块层级图的绘制 【数据介绍】Instacart Market Basket Analysis 1. 数据说明 数据共有300 0000orders, 20 0000users, …...

C++研发笔记8——C语言程序设计初阶学习笔记6
在第一部分——课前准备的学习中,我就提到了学习C语言的过程中,练习是必不可少的环节,所以本篇笔记我们来进行记录我们学习《C语言程序设计初阶》阶段的第一篇练习文章。 题目一 下面哪个不是C语言内置的数据类型: A.char B.d…...

0softmax和背后的最大熵(极大似然法)
只要无穷阶矩都一样,那么两个分布一定一样。 整理思路:1、设定样本的概率模型与目标概率模型一致(两个模型特性函数一致)建立服从伯努利分布的变量(此处需要理解样本空间及变量的关系)对两个模型进行降维&a…...

6,000 个网站上的假 WordPress 插件提示用户安装恶意软件
黑客使用窃取的凭证感染 WordPress 网站,并向其发送虚假插件,通过虚假的浏览器更新提示向最终用户发送恶意软件和信息窃取程序。 该恶意活动基于ClickFix假浏览器更新恶意软件的新变种,自 2024 年 6 月以来已使用假 WordPress 插件感染了超过…...
最新Compose环境版本
1.gradle gradle 8.10.2 插件版本 8.7.1 Android Studio Ladybug | 2024.2.1 Patch 1 2.model 下 的 build.gradle.kts plugins {alias(libs.plugins.android.application)alias(libs.plugins.kotlin.android)alias(libs.plugins.kotlin.compose)id("kotlin-kapt&qu…...

流媒体协议.之(RTP,RTCP,RTSP,RTMP,HTTP)(一)
闲着没事做,记录一下开发项目用过的协议,项目中,大多是是实时显示播放的,通过私有协议,传输到上位机,实时播放,延时小于200ms,仿照这些协议,定义的数据格式。如果用这些协…...

【智能大数据分析 | 实验四】Spark实验:Spark Streaming
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘,以提取有价值的信息和洞察。它结合了大数据技术、人工智能(AI)、机器学习(ML&a…...

es实现自动补全
目录 自动补全 拼音分词器 安装拼音分词器 第一步:下载zip包,并解压缩 第二步:去docker找到es-plugins数据卷挂载的位置,并进入这个目录 第三步:把拼音分词器的安装包拖到这个目录下 第四步:重启es 第…...

【日志】Unity3D模型导入基本问题以及浅谈游戏框架
2024.10.22 真正的谦逊从来不是人与人面对时的谦卑,而是当你回头看那个曾经的自己时,依旧保持肯定与欣赏。 【力扣刷题】 暂无 【数据结构】 暂无 【Unity】 导入外部模型资源报错问题 在导入外部资源包的时候一般都会报错,不是这个资源模…...

1.8K Star,简洁易用 Web 端创意画板
Hi,骚年,我是大 G,公众号「GitHub 指北」会推荐 GitHub 上有趣有用的项目,一分钟 get 一个优秀的开源项目,挖掘开源的价值,欢迎关注。 在数字创作的时代,找到一款功能强大且易于使用的绘图工具…...
WPF中的<Style.Triggers>
Triggers介绍 在XAML中,Triggers是Style元素的一部分,用于定义在特定条件触发时应用的样式更改。这些触发器可以响应各种事件和属性值的变化,例如控件的状态变化(如鼠标悬停、焦点状态)、数据绑定值的变化等。 以下是…...
pod相关面试题总结(持续更新)
1:当一个Pod有多个容器时,如果连接到指定的容器? #查看当前空间下的pod [rootmaster210 pods]# kubectl get pods NAME READY STATUS RESTARTS AGE linux85-nginx-tomcat 2/2 Running 0 63s [rootmaster210 …...

Matlab学习03-符号的替换及运算(接上一篇)
在上一篇的学习中,我知道了符号变量的声明👇 Matlab学习02-matlab中的数据显示格式及符号变量-CSDN博客 接下来开始学习符号运算相关的内容,并学习最为核心的matlab程序设计。之前的学习都是为了程 序设计做铺垫,程序设计又是为了…...
Windows中API-磁盘管理笔记
硬盘是由一组堆积的盘片组成类似于圆柱体组成,每个盘片的数据都以电磁方式存储在同心圆或轨道中,轨道的最小可寻址单元是扇区;基本磁盘:最常用于windows的存储类型,指的是**包含分区的磁盘。**在基本磁盘上只能创建和删…...

010 操作符详解 上
写代码的实质是在写方法体 —— 刘铁猛 操作符概览 操作符本质 操作符的本质是函数的“简记法” 操作符 简写Add函数 34 等同Add(3,4)操作符不能脱离与它关联的数据类型可以说操作符就是与固定数据类型关联的一套算法的简记法 如下图所示算法的简记法 操作符的优先级 可以使…...
【贪心算法】(第十篇)
目录 加油站(medium) 题目解析 讲解算法原理 编写代码 单调递增的数字(medium) 题目解析 讲解算法原理 编写代码 加油站(medium) 题目解析 1.题目链接:. - 力扣(LeetCode&a…...
029.爬虫专用浏览器-抓取跨域#document下的内容
一、iframe下的#document是什么 #document 是一个特殊的 HTML 元素,表示 <iframe> 元素内部的文档对象。当你在 HTML 页面中嵌入一个 <iframe> 元素时,浏览器会创建一个新的文档对象来表示 <iframe> 内部的内容。这 个文档对象就是 #…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...