当前位置: 首页 > news >正文

【多线程操作】线程池模拟实现

目录

一.线程池的作用

二.线程池的模拟实现

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.线程模块&#xff08;Thread.hpp&#xff09;&#xff1a; 2.线程锁模块&#xff08;LockGuard.hpp&#xff09;&#xff1a; 3.任务模块&#xff08;Task.hpp&#xff09; 4.线程池核心&#xff08;ThreadPool.hpp&#xff…...

HBase---Hbase安装(单机版)

Hbase安装单机版 文章目录Hbase安装单机版Master/Slave架构安装步骤配置Hbase1.上传压缩包解压更名修改hbase-env.sh修改hbase-site.xml配置HBase环境变量配置Zookeeper复制配置文件修改zoo.cfg配置文件修改myid配置Zookeeper环境变量刷信息配置文件启动hbase步骤hbase shellMa…...

启动项管理工具Autoruns使用实验(20)

实验目的 &#xff08;1&#xff09;了解注册表的相关知识&#xff1b; &#xff08;2&#xff09;了解程序在开机过程中的自启动&#xff1b; &#xff08;3&#xff09;掌握Autoruns在注册表和启动项方面的功能&#xff1b;预备知识 注册表是windows操作系统中的一个核心数据…...

BFD单臂回声实验详解

13.1.1BFD概念 BFD提供了一个通用的、标准化的、介质无关的、协议无关的快速故障检测机制,有以下两大优点: 对相邻转发引擎之间的通道提供轻负荷、快速故障检测。 用单一的机制对任何介质、任何协议层进行实时检测。 BFD是一个简单的“Hello”协议。两个系统之间建立BFD会…...

详解JAVA类加载器

目录 1.概述 2.双亲委派 3.ServiceClassLoader 4.URLClassLoader 5.加载冲突 1.概述 概念&#xff1a; 类加载器&#xff08;Class Loader&#xff09;是Java虚拟机&#xff08;JVM&#xff09;的一个重要组件&#xff0c;负责加载Java类到内存中并使其可以被JVM执行。类…...

记录一些常用C标准库函数,以及Linux系统调用函数的作用(不断更新)

C标准库函数 perror() 函数 作用&#xff1a;perror函数是C标准库中的一种函数&#xff0c;用于在STDERR&#xff08;标准错误输出流&#xff09;中输出给定的错误信息字符串。它不属于Linux系统调用函数。 具体使用方法&#xff1a;perror("调用的函数名") 所需…...

RK3568平台开发系列讲解(显示篇)DRM的atomic接口

🚀返回专栏总目录 文章目录 一、Property二、Standard Properties三、代码案例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢目前DRM主要推荐使用的是 Atomic(原子的) 接口。 一、Property Property(属性)—– Atomic操作必须依赖的基本元素 Property把前面的…...

2022年MathorCup数学建模C题自动泊车问题解题全过程文档加程序

2022年第十二届MathorCup高校数学建模 C题 自动泊车问题 原题再现 自动泊车是自动驾驶技术中落地最多的场景之一&#xff0c;自动泊车指在停车场内实现汽车的自动泊车入位过程&#xff0c;在停车空间有限的大城市&#xff0c;是一个比较实用的功能&#xff0c;减少了驾驶员将…...

【需求响应】基于数据驱动的需求响应优化及预测研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…...

Bellman-ford和SPFA算法

目录 一、前言 二、Bellman-ford算法 1、算法思想 2、算法复杂度 3、判断负圈 4、出差&#xff08;2022第十三届国赛&#xff0c;lanqiaoOJ题号2194&#xff09; 三、SPFA算法&#xff1a;改进的Bellman-Ford 1、随机数据下的最短路问题&#xff08;lanqiaoOJ题号1366&…...

假如你知道这样的MySQL

数据库三范式是什么? 第一范式&#xff08;1NF&#xff09;&#xff1a;字段具有原子性,不可再分。(所有关系型数据库系 统都满足第一范式数据库表中的字段都是单一属性的&#xff0c;不可再分)第二范式&#xff08;2NF&#xff09;是在第一范式&#xff08;1NF&#xff09;的…...

SpringBoot笔记(一)入门使用

一、为什么用SpringBootSpringBoot优点创建独立Spring应用内嵌web服务器自动starter依赖&#xff0c;简化构建配置自动配置Spring以及第三方功能提供生产级别的监控、健康检查及外部化配置无代码生成、无需编写XMLSpringBoot缺点人称版本帝&#xff0c;迭代快&#xff0c;需要时…...

C++20 协程体验

1 介绍协程是比线程更加轻量级并发编程方式&#xff0c;CPU资源在用户态进行切换,CPU切换信息在用户态保存。协程完成异步的调用流程&#xff0c;并对用户展示出同步的使用方式。协程的调度由应用层决定&#xff0c;所以不同的实现会有不同的调度方式&#xff0c;调度策略比较灵…...

这三个小事你做HIGG FEM时要知道

【这三个小事你做HIGG FEM时要知道】1.为什么做了Higg FEM 自评后要做验证&#xff1f;「自评 验证」Higg FEM 是一个持续改善的框架方法&#xff0c;来帮助工厂实现持续的环保改善&#xff0c;是一个最基本的要求&#xff0c;如果工厂期望得到一个更加客观的评价&#xff0c;…...

.net6 wpf程序一个内存不断增长问题的解决方法

一个.net6的应用程序&#xff0c;底层不断采集数据。使用wpf制作了一个简单的界面显示数据接收的情况。程序中引用了 Material Design UI框架。当程序长时间运行时发现内存在不断增长。一个星期后工作集占用内存达到1GB。使用dotnet-dump工具收集内存使用情况&#xff0c;并且分…...

NICEGUI---ROS开发之中常用的GUI工具

0. 简介 对于ROS来说&#xff0c;如果不具备一定知识的人员来使用这些我们写的算法&#xff0c;如果说没有交互&#xff0c;这会让用户使用困难&#xff0c;所以我们需要使用GUI来完成友善的数据交互&#xff0c;传统的GUI方法一般有PYQT这类GUI方法&#xff0c;但是这类GUI工…...

高盐废水除钙镁的技术解析

高盐废水指含有机物和至少总溶解固体(totaldissolvedsolids&#xff0c;tds)的质量分数大于3.5&#xff05;的废水&#xff0c;具有水量大&#xff0c;无机盐离子k、na、ca2、mg2、cl-、so42-等含量高&#xff0c;水质水量变化大&#xff0c;成分复杂&#xff0c;难生化降解等特…...

回文日期门牌制作

题目&#xff1a; 题目描述 如果将这个日期按 “yyyymmdd” 的格式写成一个 8 位数是 20200202&#xff0c;恰好是一个回文数。我们称这样的日期是回文日期。20200202 并不仅仅是一个回文日期&#xff0c;还是一个 ABABBABA 型的回文日期。 给定一个 8 位数的日期&#xff0c;请…...

基于半车悬架的轴距预瞄与轴间预瞄仿真对比

目录 前言 1. 半车悬架模型 2.轴距预瞄(单点预瞄)和轴间预瞄(两点预瞄)原理与仿真分析 2.1轴距预瞄(单点预瞄) 2.1.1预瞄原理 2.2.轴间预瞄(两点预瞄) 2.2.1预瞄原理 2.3仿真分析 3.总结 前言 对于悬架而言&#xff0c;四个车轮实际的输入信息是受到前后延时以及左右相…...

Linux开发 安装JDK8、p4

前面的笔记&#xff1a; Linux 学习笔记1 安装linux详细教程_linux系统 setting_O丶ne丨柒夜的博客-CSDN博客 Linux 学习笔记2 常用命令_O丶ne丨柒夜的博客-CSDN博客 Linux 学习笔记3 权限管理 定时任务 网络配置_O丶ne丨柒夜的博客-CSDN博客 安装配置 安装配置JDK8 Java …...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...