【linux线程(四)】初识线程池手撕线程池

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:Linux从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学更多操作系统知识
  🔝🔝


在这里插入图片描述

Linux线程池

  • 1. 前言
  • 2. 什么是池化技术?
  • 3. 线程池详解
  • 4. 手撕线程池
  • 5. 初识读写锁
  • 6. 如何快速实现简易的线程池?
  • 7. 总结以及拓展

1. 前言

线程池在校招面试阶段经常被要求手撕,可见它的重要性如何.

本章重点:

本篇文章会先介绍什么是池化技术,然后详细讲解什么是线程池,以及如何手撕线程池,并且会给大家拓展如何将线程池设计为单例模式,以及读写锁的使用方法,最后会讲解如何在校招中遇见手撕线程池时,快速的写出代码


2. 什么是池化技术?

大家可能听说过线程池,进程池,对象池,甚至是内存池等概念,那么到底什么是池?它们有什么共同特质?

池化技术:

池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的资源 .

说白了,就是线程池就是在程序启动时就创建多个线程来备用,同理对象池和内存池也就是创建多个对象/空间备用

池化技术的优点:

  • 提高性能。通过重用资源,减少了创建和销毁资源的时间,从而提高了资源的使用效率
  • 降低系统开销, 避免了频繁地向操作系统申请和释放资源的开销
  • 简化代码。通过封装资源管理逻辑,使得应用程序代码更简洁易懂

3. 线程池详解

什么是线程池:

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度

线程池的运用场景:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没
    有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

线程池的使用方法:

  1. 创建固定数量的线程,循环的去任务队列中拿任务
  2. 获取到任务后,不同线程执行不同任务的代码
  3. 任务结束,此线程继续循环的去任务队列拿任务

其次,由于线程池是会在多线程下跑的
所以将它设计为单例模型最好

除此之外,根据上一节学习到的内容,可以窥探到,线程池的本质其实就是一个生产者消费者模型,所以也会涉及到加解锁的问题,所以在类中我们需要两把锁,一把是用于单例模式的互斥锁,还有一把是用于生产者消费者之间的互斥锁


4. 手撕线程池

在写线程池的代码之前,需要先写一个关于单个线程的类,并且在线程池中,用数组存储所有的线程类

thread.hpp文件:

typedef void *(*fun_t)(void *);//线程要执行的函数是参数和返回值都为void*
class ThreadData
{
public:void *args_;//线程拥有的数据std::string name_;//线程的名字
};
class Thread
{
public:Thread(int num, fun_t callback, void *args) : func_(callback){char nameBuffer[64];snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", num);name_ = nameBuffer;tdata_.args_ = args;tdata_.name_ = name_;}void start(){pthread_create(&tid_, nullptr, func_, (void*)&tdata_);//将线程名和参数都传给线程函数}void join(){pthread_join(tid_, nullptr);}std::string name(){return name_;}~Thread(){}
private:std::string name_;//线程名字fun_t func_;//线程要执行的函数ThreadData tdata_;//线程的名字和数据pthread_t tid_;//线程ID
};

除此之外,还需要写一个锁相关的类
利用对象生命周期管理资源:

lockguard.hpp文件:

class Mutex
{
public:Mutex(pthread_mutex_t *mtx):pmtx_(mtx){}void lock() {// std::cout << "要进行加锁" << std::endl;pthread_mutex_lock(pmtx_);}void unlock(){// std::cout << "要进行解锁" << std::endl;pthread_mutex_unlock(pmtx_);}~Mutex(){}
private:pthread_mutex_t *pmtx_;
};
// RAII风格的加锁方式
class lockGuard
{
public:lockGuard(pthread_mutex_t *mtx):mtx_(mtx){mtx_.lock();}~lockGuard(){mtx_.unlock();}
private:Mutex mtx_;
};

最后再看看看主要的函数:

在ThreadPool.hpp文件中:

const int g_thread_num = 3;//创建的线程数量
// 本质是: 生产消费模型
template <class T>
class ThreadPool
{
public:pthread_mutex_t *getMutex(){return &lock;}bool isEmpty()//判断队列是否为空{return task_queue_.empty();}void waitCond(){pthread_cond_wait(&cond, &lock);}T getTask()//拿到队列中的任务{T t = task_queue_.front();task_queue_.pop();return t;}private:ThreadPool(int thread_num = g_thread_num) : num_(thread_num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);for (int i = 1; i <= num_; i++){threads_.push_back(new Thread(i, routine, this));//将创建出来的线程用数组管理}}ThreadPool(const ThreadPool<T> &other) = delete; //禁用拷贝构造函数const ThreadPool<T> &operator=(const ThreadPool<T> &other) = delete; //禁用运算符重载=
public:// 考虑一下多线程使用单例的过程static ThreadPool<T> *getThreadPool(int num = g_thread_num){// 可以有效减少未来必定要进行加锁检测的问题// 拦截大量的在已经创建好单例的时候,剩余线程请求单例的而直接访问锁的行为if (nullptr == thread_ptr) {lockGuard lockguard(&mutex);// 但是,未来任何一个线程想获取单例,都必须调用getThreadPool接口// 但是,一定会存在大量的申请和释放锁的行为,这个是无用且浪费资源的if (nullptr == thread_ptr)thread_ptr = new ThreadPool<T>(num);}return thread_ptr;}// 1. run()void run()//让线程池跑起来,也就是创建出多个线程{for (auto &iter : threads_){iter->start();std::cout << iter->name() << " 启动成功" << std::endl;}}// 线程池本质也是一个生产消费模型// void *routine(void *args)// 消费过程static void *routine(void *args)//线程拿到任务后要执行的函数{ThreadData *td = (ThreadData *)args;ThreadPool<T> *tp = (ThreadPool<T> *)td->args_;while (true){T task;{lockGuard lockguard(tp->getMutex());//去队列中拿任务前需要先加锁while (tp->isEmpty())//若队列为空,则使用条件变量进行等待tp->waitCond();// 读取任务task = tp->getTask(); // 任务队列是共享的-> 将任务从共享,拿到自己的私有空间}//上来加上一对花括号的原因是让锁对象出了作用域自动销毁//此处来处理任务}}void pushTask(const T &task){lockGuard lockguard(&lock);//插入任务时,也要加锁,队列是临界资源task_queue_.push(task);pthread_cond_signal(&cond);//插入成功后,直接使用条件变量唤醒线程来拿任务}~ThreadPool(){for (auto &iter : threads_){iter->join();delete iter;}pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}
private:std::vector<Thread *> threads_;int num_;std::queue<T> task_queue_;static ThreadPool<T> *thread_ptr;static pthread_mutex_t mutex;pthread_mutex_t lock;pthread_cond_t cond;
};
template <typename T>
ThreadPool<T> *ThreadPool<T>::thread_ptr = nullptr;
template <typename T>
pthread_mutex_t ThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER;

5. 初识读写锁

其实出了互斥锁外,还有其他种类的锁:

在这里插入图片描述
而将要介绍的锁是读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

说白了就是一把锁,可以让只读的线程无限制的进入,而只对要对数据做修改的线程加锁

在这里插入图片描述
读写锁的操作方法:

  1. 初始化锁
    在这里插入图片描述

  2. 销毁锁
    在这里插入图片描述

  3. 加解锁

在这里插入图片描述


6. 如何快速实现简易的线程池?

可以发现,上面的代码量是巨大的,所以在实际面试中遇见了,多半是记不住这么多代码的,所以这里我给大家打个样,写一个简易版的线程池,用来应对校招中需要手撕的场景:

首先需要简化的是,直接用一个整数代表要创建线程的数量,而不使用数组存储.并且简化线程要执行的函数,情况如下:

#include<iostream>
#include<pthread.h>
#include<mutex>
#include<queue>
#include<functional>
using namespace std;
pthread_mutex_t MTX = PTHREAD_MUTEX_INITIALIZER;//用于初始化单例模式中要使用的锁
typedef function<void(int)> func_t;
void myprintf(int x)//模拟线程要执行的函数
{cout<<x<<" "<<endl;
}
class Task   //模拟线程要执行的任务
{
public:Task(func_t func = myprintf):_func(func){}
public:func_t _func;
};
class ThreadPool
{
public:static ThreadPool* GetInstance(){if(_singleton == nullptr){pthread_mutex_lock(&MTX);if(_singleton == nullptr){_singleton = new ThreadPool();_singleton->InitThreadPool();}pthread_mutex_unlock(&MTX);}return _singleton;}void InitThreadPool()//启动线程池{for(int i=0;i<_num;i++){pthread_t tid;if(pthread_create(&tid,nullptr,Routine,this)!=0)cout<<"线程启动失败"<<endl;}cout<<"线程池启动成功"<<endl;}static void* Routine(void* args){ThreadPool* td = (ThreadPool*)args;while(1){//拿到任务区执行Task t;pthread_mutex_lock(&td->_mtx);//加锁while(td->TaskQueueIsEmpty())//若资源不就绪就等待pthread_cond_wait(&td->_cond,&td->_mtx);cout<<"开始执行任务"<<endl;td->Pop(t);//任务队列的任务减一pthread_mutex_unlock(&td->_mtx);t._func(10);cout<<"一次任务执行完毕"<<endl;}}void Push(Task t){pthread_mutex_lock(&_mtx);_q.push(t);pthread_mutex_unlock(&_mtx);pthread_cond_signal(&_cond);cout<<"任务push成功"<<endl;}void Pop(Task& t){t = _q.front();_q.pop();}bool TaskQueueIsEmpty(){return _q.size()==0?true:false;}
private:ThreadPool(int num = 10):_num(num){pthread_mutex_init(&_mtx,nullptr);pthread_cond_init(&_cond,nullptr);}ThreadPool(const ThreadPool& td) = delete;
public:static ThreadPool* _singleton;queue<Task> _q;int _num;//创建线程的数量pthread_mutex_t _mtx;pthread_cond_t _cond;
};
ThreadPool* ThreadPool::_singleton = nullptr;

上面是线程池的简易版,本人也是硬背的这段代码来应对手撕


7. 总结以及拓展

大家可能现在理解了线程的重要性,像12305铁路系统这种软件,它一秒钟可能会有百万个人同时上线,如果没有像线程池或者其他技术支持,那么一旦这么多人登陆12305软件,服务器肯定会直接崩溃,当然这里只是举一个线程池实际运用的例子,实际生活中的例子肯定不会像直接使用一个线程池这么简单,所以,respect!


🔎 下期预告:Linux网络基础 🔍

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

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

相关文章

Python 从0开始 一步步基于Django创建项目(3)使用Admin site管理数据模型

本文内容建立在《Python 从0开始 一步步基于Django创建项目&#xff08;2&#xff09;创建应用程序&数据模型》的基础上。 Django提供的admin site&#xff0c;使得网站管理员&#xff0c;能够轻松管理网站的数据模型。 本文首先创建‘管理员账户’&#xff0c;即超级用户…

华为OD机22道试题

华为OD机试题 2.查找小朋友的好朋友位置 在学校中&#xff0c;N 个小朋友站成一队&#xff0c;第 i 个小朋友的身高为 height[i]&#xff0c;第 i 个小朋友可以看到第一个比自己身高更高的小朋友j&#xff0c;那么 j 是 i 的好朋友 (要求&#xff1a;j>i) 。 请重新生成一个…

202305 CSP认证

202305-1 重复局面 第一题直接干 #include<bits/stdc.h> using namespace std; unordered_map<string, int> chess; int main() {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);string line, str ""; int n;cin >> n;while(n --){str …

数据结构:链式队列

1.设计思想&#xff1a; 我们可以设计出以上五种队列&#xff0c;但是基于时间复杂度&#xff0c;和空间复杂度的最优解&#xff0c;我们选择入队和出队均为O(1)的&#xff0c;也就是第五种 2.结构设计 typedef struct LPNode//数据节点 {int data;//数据struct LPNode* next…

Redis消息队列与thinkphp/queue操作

业务场景 场景一 用户完成注册后需要发送欢迎注册的问候邮件、同时后台要发送实时消息给用户对应的业务员有新的客户注册、最后将用户的注册数据通过接口推送到一个营销用的第三方平台。 遇到两个问题&#xff1a; 由于代码是串行方式&#xff0c;流程大致为&#xff1a;开…

基于STC12C5A60S2系列1T 8051单片机可编程计数阵列CCP/PCA/PWM模块的捕获模式(外部中断)应用

基于STC12C5A60S2系列1T 8051单片机可编程计数阵列CCP/PCA/PWM模块的捕获模式(外部中断)应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍STC12C5A60S2系列1T 805…

【Langchain-Chatchat】部署ChatGLM3-6B-32K教程

介绍 Langchain-Chatchat这个框架可以帮助我们更容易的部署大语言模型&#xff0c;之前也写过ChatGLM传统的部署教程&#xff0c;有兴趣的可以参考 【ChatGLM3】第三代大语言模型多GPU部署指南【ChatGLM2-6B】从0到1部署GPU版本 借助Langchain-Chatchat框架&#xff0c;可以…

32串口学习

基于之前的GPIO等工程&#xff0c;后面的上手难度就简单多了&#xff0c;主要是相关寄存器的设置。 void USART1_Config(void) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;/* config USART1 clock */RCC_APB2PeriphClockCmd(RCC_APB2Periph…

计算机网络:信道复用技术概念解析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

数据结构从入门到精通——希尔排序

希尔排序 前言一、希尔排序( 缩小增量排序 )二、希尔排序的特性总结三、希尔排序动画演示四、希尔排序具体代码实现test.c 前言 希尔排序是一种基于插入排序的算法&#xff0c;通过比较相距一定间隔的元素来工作&#xff0c;各趟比较所用的距离随着算法的进行而减小&#xff0…

【前端】Web API

1.Web API 简介 JS分为三大部分&#xff1a; ESCMScript&#xff1a;基础语法部分DOM API&#xff1a;操作页面结构BOM API&#xff1a;操作浏览器 Web API包含 DOM BOM 2.DOM基本概念 DOM全称 Document Object Mod…

最短路算法

数据结构、算法总述&#xff1a;数据结构/算法 C/C-CSDN博客 目录 朴素dijkstra算法 堆优化版dijkstra算法 Bellman-Ford算法 spfa 算法&#xff08;队列优化的Bellman-Ford算法&#xff09; spfa判断图中是否存在负环 floyd算法 朴素dijkstra算法 思路&#xff1a; 集合…

Linux相关命令(2)

1、W &#xff1a;主要是查看当前登录的用户 在上面这个截图里面呢&#xff0c; 第一列 user &#xff0c;代表登录的用户&#xff0c; 第二列&#xff0c; tty 代表用户登录的终端号&#xff0c;因为在 linux 中并不是只有一个终端的&#xff0c; pts/2 代表是图形界面的第…

长三角科技盛会“2024南京国际人工智能,机器人,自动驾驶展览会”

2024南京国际人工智能,机器人,自动驾驶展览会 2024 Nanjing International Ai, Robotics, Autonomous Driving Expo 时间:2024年11月22-24日 地点:南京国际博览中心 南京&#xff0c;这座历史悠久的文化名城&#xff0c;如今正站在新一轮科技产业变革的前沿&#xff0c;以人工…

院子摄像头的监控

院子摄像头的监控和禁止区域入侵检测相比&#xff0c;多了2个功能&#xff1a;1&#xff09;如果检测到有人入侵&#xff0c;则把截图保存起来&#xff0c;2&#xff09;如果检测到有人入侵&#xff0c;则向数据库插入一条事件数据。 打开checkingfence.py&#xff0c;添加如下…

Springboot笔记-03

1.properties配置文件 #配制oerson的值 person.lastname张三 person.age12 person.birth2017/12/12 person.bossfalse person.dog.namedag person.dog.age15 person.maps.k1v1 person.maps.k212 person.listsa,b,c运行结果乱码 因为idea默认是utf-8编码而properties是ascall编…

Bytebase 2.14.1 - 分支 (Branching) 功能支持 Oracle

&#x1f680; 新功能 分支 (Branching) 功能支持 Oracle。为 SQL 编辑器添加了项目选择器。 新增 SQL 审核规范&#xff1a; 禁止混合 DDL、DML 语句。禁止对同一张表进行不同类型的 DML 变更 (UPDATE,INSERT,DELETE)。 &#x1f514; 重大变更 工作空间设置中的「数据访问…

大屏可视化综合展示解决方案

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.1.系统权限设计 3.…

利用Scala与Apache HttpClient实现网络音频流的抓取

概述 在当今数字化时代&#xff0c;网络数据的抓取和处理已成为许多应用程序和服务的重要组成部分。本文将介绍如何利用Scala编程语言结合Apache HttpClient工具库实现网络音频流的抓取。通过本文&#xff0c;读者将学习如何利用强大的Scala语言和Apache HttpClient库来抓取网…

Python代码实现Excel表格转HTML文件

Excel工作簿是常用的表格格式&#xff0c;广泛用于组织、分析及展示数据。Excel文件通常需要专门的文档阅览器进行查看。如果我们想要以更兼容的方式展示Excel表格&#xff0c;可以将其转换为HTML格式&#xff0c;使其能够在各种浏览器中直接进行查看。同时&#xff0c;将Excel…