【多线程操作】线程池模拟实现
目录
一.线程池的作用
二.线程池的模拟实现
1.线程模块(Thread.hpp):
2.线程锁模块(LockGuard.hpp):
3.任务模块(Task.hpp)
4.线程池核心(ThreadPool.hpp)
一.线程池的作用
线程池是用来维护多个线程的,当我们需要大量线程执行多个不同任务的时候如果不用线程池就会面临反复创建线程执行任务后再销毁的局面,这意味着需要手动控制大量进程。使用线程池可以预先创建好线程数量避免线程创建过多占用电脑资源,同时利用rall思想可对线程进行自动控制,同样的思想也可以实现对线程的自动加锁和解锁,采用仿函数可以让线程池接受任意类型的任务。
二.线程池的模拟实现
我们可以对多个模块进行封装从而实现低内居高耦合。线程池必然包含线程的创建和销毁,可以单独提出出来创建一个线程模块。(下文中的logmessage是一个打印工程日志的函数,这里不做详细介绍)
1.线程模块(Thread.hpp):
可以把线程的创建和回收用函数封装,实现线程的初始化和回收,同时给传进来的任务通过ThreadData绑定上线程的名称
#include <string.h>
#include <iostream>
#include <pthread.h>
#include "LogMessage.hpp"typedef void *(*fun_t)(void *); //本来想使用functaion函数,但由于pthread_create不支持所以只能写成c的形式。class ThreadData //线程数据包含线程名和要执行程序的void*的形参
{
public:std::string _name;void* _arg;
};class Thread
{
public:Thread(std::string name,fun_t func,void* arg):_func(func){_tdata._name = name;_tdata._arg = arg;}std::string name(){return _tdata._name;}void start(){pthread_create(&_pid,nullptr,_func,(void *)&_tdata); //创建线程的同时执行要执行的任务logMessage(NORMAL, "线程启动成功");}void join(){pthread_join(_pid,nullptr);}~Thread(){}private:ThreadData _tdata;pthread_t _pid;fun_t _func;
};
2.线程锁模块(LockGuard.hpp):
由于在使用多线程的时候势必或遇到线程安全的问题,我们一定需要使用到锁,可以利用rall的思想实现线程创建回收的时候自动创建锁回收锁,我们可以手动传入锁,并把创建锁和释放锁放入LockGuard的构造函数和析构函数,这样就可以在使用的时候通过花括号控制锁的生命周期从而实现自动加锁解锁。
#include <pthread.h>
#include <iostream>class Mutex
{
public:Mutex(pthread_mutex_t *pmtx): _pmtx(pmtx){}~Mutex(){}void Lock(){pthread_mutex_lock(_pmtx);}void Unlock(){pthread_mutex_unlock(_pmtx);}private:pthread_mutex_t *_pmtx;
};//rall方法
class LockGuard
{
public:LockGuard(pthread_mutex_t *pmtx):_mtx(pmtx){_mtx.Lock();}~LockGuard(){//std::cout<<"unlock";_mtx.Unlock();}
private:Mutex _mtx;
};
3.任务模块(Task.hpp)
针对不同的任务我们提供一个类把它包装起来并提供一个仿函数给ThreadPool调用,这样 以后更换任务的时候就不需要更改线程池里的代码了,只需要更改task里的函数类型和仿函数调用方式就可以更换不同的任务了,这里以一个加法计算器来举例子。
#pragma once#include <iostream>
#include <string>
#include <functional>
#include "LogMessage.hpp"typedef std::function<int(int,int)> func_t; //更换函数的时候只需要更换这里的函数类型class Task
{
public:Task(){}Task(int x, int y, func_t func):_x(x),_y(y), _func(func){}void operator()(const std::string &name){logMessage(FATAL, "%s处理完成: %d+%d=%d | %s | %d",name.c_str(),_x,_y,_func(_x,_y),__FILE__, __LINE__); //更换这里的日志信息和操作即可}
public:int _x;int _y;func_t _func;
};
4.线程池核心(ThreadPool.hpp)
首先我们需要在构造函数内初始化条件变量和锁,并创建指定数量的线程,并完成所有的线程准备工作,并通过vector管理起来,当我们通过run拉起所有线程的时候,各个线程会执行routine函数,不同的线程执行不同的任务,不过当任务队列为空的时候各个线程是不会执行routine函数的,只有当向任务队列添加完任务后,并唤醒消费线程,线程才会执行各自的任务,并将任务任务队列中移除。唯一的注意点就是routine必须为static因为这样才不会传入this指针,不然不符合线程创建系统函数的格式要求。这样就确保线程在有资源的情况下跑起来了
#pragma once //防止宏定义多次定义
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "LockGuard.hpp"
#include "LogMessage.hpp"const int defult_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;}ThreadPool(int thread_num = defult_thread_num): _num(thread_num){ // 初始化锁和条件变量pthread_mutex_init(&_lock, nullptr);pthread_cond_init(&_cond, nullptr);for (int i = 0; i <= _num; i++){char nameBuffer[64];snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", i);_threads.push_back(new Thread(nameBuffer, routine, this));// 传this指针是因为rountine函数为static函数无法访问类内成员,我们需要手动传入来访问内部成员}logMessage(NORMAL, "线程池创建成功");}//消费过程static void *routine(void *arg) // 为满足线程的启动形式必须将this指针剔除{ThreadData *td = (ThreadData *)arg; // 取到线程内部的数据包(void*数据的外层)ThreadPool<T> *tp = (ThreadPool<T> *)(td->_arg); // 取到数据包内指向自己的指针,通过这种方式调用内部成员变量while (true){T task;{LockGuard lg(tp->getMutex()); logMessage(NORMAL,"申请锁成功");while(tp->isEmpty()){tp->waitcond();}task = tp->getTask(); //将任务从公共空间拿到私有空间并将公共空间对应的任务去掉}task(td->_name);//仿函数执行拿出来的任务}logMessage(NORMAL,"消费成功");}void run(){for (auto &iter : _threads) //拉起每一个线程{iter->start();logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");}}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;std::queue<T> _task_queue;pthread_mutex_t _lock;pthread_cond_t _cond;int _num; // 线程容量
};
相关文章:
【多线程操作】线程池模拟实现
目录 一.线程池的作用 二.线程池的模拟实现 1.线程模块(Thread.hpp): 2.线程锁模块(LockGuard.hpp): 3.任务模块(Task.hpp) 4.线程池核心(ThreadPool.hppÿ…...
HBase---Hbase安装(单机版)
Hbase安装单机版 文章目录Hbase安装单机版Master/Slave架构安装步骤配置Hbase1.上传压缩包解压更名修改hbase-env.sh修改hbase-site.xml配置HBase环境变量配置Zookeeper复制配置文件修改zoo.cfg配置文件修改myid配置Zookeeper环境变量刷信息配置文件启动hbase步骤hbase shellMa…...
启动项管理工具Autoruns使用实验(20)
实验目的 (1)了解注册表的相关知识; (2)了解程序在开机过程中的自启动; (3)掌握Autoruns在注册表和启动项方面的功能;预备知识 注册表是windows操作系统中的一个核心数据…...
BFD单臂回声实验详解
13.1.1BFD概念 BFD提供了一个通用的、标准化的、介质无关的、协议无关的快速故障检测机制,有以下两大优点: 对相邻转发引擎之间的通道提供轻负荷、快速故障检测。 用单一的机制对任何介质、任何协议层进行实时检测。 BFD是一个简单的“Hello”协议。两个系统之间建立BFD会…...
详解JAVA类加载器
目录 1.概述 2.双亲委派 3.ServiceClassLoader 4.URLClassLoader 5.加载冲突 1.概述 概念: 类加载器(Class Loader)是Java虚拟机(JVM)的一个重要组件,负责加载Java类到内存中并使其可以被JVM执行。类…...
记录一些常用C标准库函数,以及Linux系统调用函数的作用(不断更新)
C标准库函数 perror() 函数 作用:perror函数是C标准库中的一种函数,用于在STDERR(标准错误输出流)中输出给定的错误信息字符串。它不属于Linux系统调用函数。 具体使用方法:perror("调用的函数名") 所需…...
RK3568平台开发系列讲解(显示篇)DRM的atomic接口
🚀返回专栏总目录 文章目录 一、Property二、Standard Properties三、代码案例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢目前DRM主要推荐使用的是 Atomic(原子的) 接口。 一、Property Property(属性)—– Atomic操作必须依赖的基本元素 Property把前面的…...
2022年MathorCup数学建模C题自动泊车问题解题全过程文档加程序
2022年第十二届MathorCup高校数学建模 C题 自动泊车问题 原题再现 自动泊车是自动驾驶技术中落地最多的场景之一,自动泊车指在停车场内实现汽车的自动泊车入位过程,在停车空间有限的大城市,是一个比较实用的功能,减少了驾驶员将…...
【需求响应】基于数据驱动的需求响应优化及预测研究(Matlab代码实现)
👨🎓个人主页:研学社的博客💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密…...
Bellman-ford和SPFA算法
目录 一、前言 二、Bellman-ford算法 1、算法思想 2、算法复杂度 3、判断负圈 4、出差(2022第十三届国赛,lanqiaoOJ题号2194) 三、SPFA算法:改进的Bellman-Ford 1、随机数据下的最短路问题(lanqiaoOJ题号1366&…...
假如你知道这样的MySQL
数据库三范式是什么? 第一范式(1NF):字段具有原子性,不可再分。(所有关系型数据库系 统都满足第一范式数据库表中的字段都是单一属性的,不可再分)第二范式(2NF)是在第一范式(1NF)的…...
SpringBoot笔记(一)入门使用
一、为什么用SpringBootSpringBoot优点创建独立Spring应用内嵌web服务器自动starter依赖,简化构建配置自动配置Spring以及第三方功能提供生产级别的监控、健康检查及外部化配置无代码生成、无需编写XMLSpringBoot缺点人称版本帝,迭代快,需要时…...
C++20 协程体验
1 介绍协程是比线程更加轻量级并发编程方式,CPU资源在用户态进行切换,CPU切换信息在用户态保存。协程完成异步的调用流程,并对用户展示出同步的使用方式。协程的调度由应用层决定,所以不同的实现会有不同的调度方式,调度策略比较灵…...
这三个小事你做HIGG FEM时要知道
【这三个小事你做HIGG FEM时要知道】1.为什么做了Higg FEM 自评后要做验证?「自评 验证」Higg FEM 是一个持续改善的框架方法,来帮助工厂实现持续的环保改善,是一个最基本的要求,如果工厂期望得到一个更加客观的评价,…...
.net6 wpf程序一个内存不断增长问题的解决方法
一个.net6的应用程序,底层不断采集数据。使用wpf制作了一个简单的界面显示数据接收的情况。程序中引用了 Material Design UI框架。当程序长时间运行时发现内存在不断增长。一个星期后工作集占用内存达到1GB。使用dotnet-dump工具收集内存使用情况,并且分…...
NICEGUI---ROS开发之中常用的GUI工具
0. 简介 对于ROS来说,如果不具备一定知识的人员来使用这些我们写的算法,如果说没有交互,这会让用户使用困难,所以我们需要使用GUI来完成友善的数据交互,传统的GUI方法一般有PYQT这类GUI方法,但是这类GUI工…...
高盐废水除钙镁的技术解析
高盐废水指含有机物和至少总溶解固体(totaldissolvedsolids,tds)的质量分数大于3.5%的废水,具有水量大,无机盐离子k、na、ca2、mg2、cl-、so42-等含量高,水质水量变化大,成分复杂,难生化降解等特…...
回文日期门牌制作
题目: 题目描述 如果将这个日期按 “yyyymmdd” 的格式写成一个 8 位数是 20200202,恰好是一个回文数。我们称这样的日期是回文日期。20200202 并不仅仅是一个回文日期,还是一个 ABABBABA 型的回文日期。 给定一个 8 位数的日期,请…...
基于半车悬架的轴距预瞄与轴间预瞄仿真对比
目录 前言 1. 半车悬架模型 2.轴距预瞄(单点预瞄)和轴间预瞄(两点预瞄)原理与仿真分析 2.1轴距预瞄(单点预瞄) 2.1.1预瞄原理 2.2.轴间预瞄(两点预瞄) 2.2.1预瞄原理 2.3仿真分析 3.总结 前言 对于悬架而言,四个车轮实际的输入信息是受到前后延时以及左右相…...
Linux开发 安装JDK8、p4
前面的笔记: Linux 学习笔记1 安装linux详细教程_linux系统 setting_O丶ne丨柒夜的博客-CSDN博客 Linux 学习笔记2 常用命令_O丶ne丨柒夜的博客-CSDN博客 Linux 学习笔记3 权限管理 定时任务 网络配置_O丶ne丨柒夜的博客-CSDN博客 安装配置 安装配置JDK8 Java …...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
