当前位置: 首页 > 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; // 线程容量
};

http://www.lryc.cn/news/32367.html

相关文章:

  • HBase---Hbase安装(单机版)
  • 启动项管理工具Autoruns使用实验(20)
  • BFD单臂回声实验详解
  • 详解JAVA类加载器
  • 记录一些常用C标准库函数,以及Linux系统调用函数的作用(不断更新)
  • RK3568平台开发系列讲解(显示篇)DRM的atomic接口
  • 2022年MathorCup数学建模C题自动泊车问题解题全过程文档加程序
  • 【需求响应】基于数据驱动的需求响应优化及预测研究(Matlab代码实现)
  • Bellman-ford和SPFA算法
  • 假如你知道这样的MySQL
  • SpringBoot笔记(一)入门使用
  • C++20 协程体验
  • 这三个小事你做HIGG FEM时要知道
  • .net6 wpf程序一个内存不断增长问题的解决方法
  • NICEGUI---ROS开发之中常用的GUI工具
  • 高盐废水除钙镁的技术解析
  • 回文日期门牌制作
  • 基于半车悬架的轴距预瞄与轴间预瞄仿真对比
  • Linux开发 安装JDK8、p4
  • 基于 x86 SoC 的车辆智能驾驶舱和ADAS设计(一)
  • 类模板函数模板
  • Leetcode DAY 56: 两个字符串的删除操作 and 编辑距离
  • 系统检测维护工具Wsycheck使用(18)
  • 111 ok
  • Python API教程:API入门
  • SpringMVC学习笔记
  • Linux学习记录01
  • VScode 插件【配置】
  • 基于 Rainbond 的 Pipeline(流水线)插件
  • ASGARD:单细胞导向的药物发现