【多线程操作】线程池模拟实现
目录
一.线程池的作用
二.线程池的模拟实现
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 …...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
