Linux下设计简易线程池

news2025/1/19 23:11:52

Linux下设计简易线程池

文章目录

  • Linux下设计简易线程池
  • 1.介绍
  • 2.具体实现
    • 2.1任务类头文件Task.hpp
    • 2.2线程池文件ThreadPool.hpp
    • 2.3主函数Main.cc

1.介绍

​ 线程池是一种池化技术,是消费者生产者模型的具体体现。它能够预先创建一批能够被重复使用的线程,而无需创建任何额外的空间。因为减少了线程的创建和开销,所以提高了系统的性能和资源的利用率

在这里插入图片描述

话不多说,在知道具体实现之前,我们先看看一个简易的线程池能够达到的效果:

目的: 运行5个线程完成加减乘除的任务

在这里插入图片描述

可以看到,我们运行起来后会有包括主线程在内的6个线程运行起来。在任务方面,我们可以实现每个线程都完成一个任务并返回结果的现象。那么事不宜迟,我们赶快来了解怎么实现一个简易线程池吧!

2.具体实现

​ 首先我们要知道需要了解的知识:

1.线程的使用以及创建(pthread)

2.互斥锁的使用(mutex)

3.条件变量的使用(Condition Variable )


我们的目标:通过一个数组(vector)里存放线程,然后分批完成任务队列(Queue)里的目标,线程任务有则处理,无则等待。期间需要注意使用互斥锁保证原子性,在必要的条件我们需要使用条件变量来唤醒或者等待。在主函数内输入我们的通过输入的数据构建出一个个任务对象,再把任务对象Push进入线程池类中分配线程处理即可。

​ **代码的结构:**为了使结构清晰,我们打算分三个文件来处理:

1.主函数Main.cc

2.任务类头文件Task.hpp

3.线程池类文件ThreadPool.hpp

在这里插入图片描述

2.1任务类头文件Task.hpp

​ 在之前我们已经提过了,我们采用单次加减乘除的任务来模拟日常任务交给线程处理。所以我们的任务Task类就好设计了。

​ 我们类中成员中只用有四个元素即可:第一个数x,第二个数y,操作符ops,结果result ,之后再获取到元素后看成Task对象重写仿函数处理即可。

//模拟任务:实现加减乘除的任务
#pragma once
#include<iostream>
#include<string>
#include<unistd.h>

class Task
{
    public:
    Task(){}
    Task(int x ,int y,char ops)
    :_x(x)
    ,_y(y)
    ,_ops(ops)
    ,_result(0)
    {}

    ~Task(){}

    void operator()() //仿函数
    {
        switch(_ops)
        {
            case '+':
            {
            _result = _x + _y;
            break;
            }

            case '-':
            {
            _result = _x - _y;
            break;
            }

            case '*':
            {
            _result = _x * _y;
            break;
            }

            case '/':
            {
            if(_y == 0) 
            {
              std::cout<<"zero Error..."<<std::endl;
              break;
            }
            
            _result = _x / _y;
            break;
            }
        
            default:
                break;
            
        }
        usleep(100000);
    }

    std::string Result() //返回结果
    {
        return std::to_string(_result);
    }

    private:    //_x + _ y + _ops = _result
    int _x;
    int _y;
    char _ops;
    int _result;

};

2.2线程池文件ThreadPool.hpp

在这个线程池类成员中我们打算里面含有:

1.线程存放数组_threads

2.线程个数_num

3.任务队列_tasks

4.互斥锁mtx

5.条件变量_cond

线程池文件代码中有一下这些思想,待会我们展示代码的时候会用到,我们先提前写出来,之后可以跟代码复核:

1.线程池中我们打算采用RAII(资源生命周期随着对象生命周期)思想,线程池的创建(构造)与释放(析构)就包含了互斥锁、条件变量的初始化与销毁。

2.每个线程处理的函数由于是在类内,我们需要定义成static的,否则是不允许调用非静态成员的。既然是类内成员,我们就需要注意隐含有一个this指针,否则可能导致pthread_create创建线程中传参不匹配的问题。

3.每个线程处理的函数很显然是需要上锁的,如果线程队列中已经没有线程可用了,我们就需要使用条件变量等待。

4.线程处理的函数既然是static的,就无法直接使用类内的私有成员函数,就需要定义一批调用函数来间接拿到每个线程的成员如锁、条件变量进行加减锁、唤醒等待。

5.线程池类中当然包括放入任务函数和删除任务的函数,并且需要加锁

具体对应看代码:

#pragma once

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<unistd.h>
#include<pthread.h>
#include "Task.hpp"

const static int N = 5;

template<class T>
class ThreadPool
{
    public:

    ThreadPool(int num = N) //带参构造
    :_num(num)
    ,_threads(num)
    {
        pthread_mutex_init(&mtx,nullptr);
        pthread_cond_init(&_cond,nullptr);
    }

    ~ThreadPool()          //析构函数
    {
        pthread_mutex_destroy(&mtx);
        pthread_cond_destroy(&_cond);
    }
		
    //-----------加减锁、唤醒等待的操作接口-----------
    void GetLocked() {pthread_mutex_lock(&mtx);}
    void GetUnlocked() {pthread_mutex_unlock(&mtx);}
    void ThreadWait() {pthread_cond_wait(&_cond,&mtx);}
    void ThreadWakeup() {pthread_cond_signal(&_cond);}
    bool isEmpty(){return _tasks.empty();}
    //--------------------------------------


    //放入任务的函数 //需要上锁保证线程安全
    T PushTask(const T& t)
    {
        GetLocked();
        _tasks.push(t);
        ThreadWakeup();
        GetUnlocked();

    }
    //删除任务
    T PopTask()
    {
       T t = _tasks.front();
       _tasks.pop();
       return t;
    }

    

    //启动函数
    void Start()
    {
        for(int i =0;i<_num;i++)
        {
            //在类内部一定要注意多一个this指针参数,否则小心传参不正确
            pthread_create(&_threads[i],nullptr,threadRoutine,this); 
        }
    }
    //--------------------------------------------------

    //线程执行函数 (类内需要加上static)否则类内不允许调用内部元素
   static void* threadRoutine(void* args)
   {
    pthread_detach(pthread_self());//先线程自我分离

    ThreadPool<T>* tp = static_cast<ThreadPool<T>*>(args); //将当前对象的地址传入this类内中调用
    while(true)//线程执行的任务也需要上锁保证原子性
    {
        //这里需要检测有没有任务
        //有则处理,无则等待
        //细节也是需要加锁
        tp->GetLocked();
        while(tp->isEmpty()) //如果线程队列为空则任务需要等待
        {
            tp->ThreadWait();
        }
        T t = tp->PopTask(); //从公共区域拿到线程私有区域
        tp->GetUnlocked();

        t();//执行任务
        //输出结果
        std::cout<< "thread handler done,result: "<<t.Result()<<std::endl;
    }
   }
//-----------------------------------------------------------------------
    private:
    std::vector<pthread_t> _threads; //线程存放数组
    int _num; //线程个数
    std::queue<T> _tasks; //任务队列

    pthread_mutex_t mtx; //互斥锁
    pthread_cond_t _cond;//条件变量
};

2.3主函数Main.cc

在主函数中我们为了让RAII思想(资源生命周期随着对象生命周期)得到体现,采用智能指针unique_ptr来让资源得到创建和销毁。

并且我们需要在主函数中获取我们执行加减乘除任务的参数,并把它实例化成任务对象推送到线程池解决


#include "ThreadPool.hpp"
#include "Task.hpp"
#include<unistd.h>
#include<memory>

int main()
{   
    std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());
    tp->Start(); //调用线程池中的创建线程的函数

    while(true)
    {
        int x;
        int y;
        char ops;
        
        std::cout<<"Please Enter x:";
        std::cin >> x;
        std::cout<<"Please Enter y:";
        std::cin >> y;
        std::cout<<"Please Enter ops(+=*/):";
        std::cin >> ops;

        Task t(x,y,ops);

        tp->PushTask(t);
        sleep(1);
    }

    return 0;
}

之后一综合运行就能看到我们一开始看到的结果啦!至此一个简易的线程池就实现完了
在这里插入图片描述

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

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

相关文章

Java是编译型还是解释型

定义 编译语言&#xff08;英语&#xff1a;Compiled language&#xff09;是一种程式语言类型&#xff0c;通过编译器来实作。它不像直译语言一样&#xff0c;由直译器将程式码一句一句执行&#xff0c;而是以编译器&#xff0c;先将程式码编译为机器码&#xff0c;再加以执行…

麦肯锡发布《2023年度科技报告》!

在经历了 2022 年技术投资和人才的动荡之后&#xff0c;2023 年上半年&#xff0c;人们对技术促进商业和社会进步的潜力重新燃起了热情。生成式人工智能&#xff08;Generative AI&#xff09;在这一复兴过程中功不可没&#xff0c;但它只是众多进步中的一个&#xff0c;可以推…

世纪之争:量子物理学解决了「黑洞悖论」

在黑洞内部&#xff0c;20 世纪物理学的两大理论支柱似乎发生了冲突。现在&#xff0c;一群年轻的物理学家认为&#xff0c;他们通过诉诸新世纪的中心支柱——量子信息物理学&#xff0c;已经解决了这一冲突。 2013 年 8 月&#xff0c;数十位著名理论物理学家齐聚加利福尼亚州…

Cpp学习——string模拟实现

目录 一&#xff0c;string的成员变量 二&#xff0c;string的各项功能函数 1.构造函数 2.析构函数 3.扩容函数 4.插入与删除数据的函数 5.运算符重载 6.打印显示函数 7&#xff0c;拷贝构造 8.find函数 一&#xff0c;string的成员变量 在模拟实现string之前&#xff…

怎么把视频转gif图片?视频在线转gif动图的方法

我们在使用gif动图的时候&#xff0c;经常发现有些图片是一些视频片段&#xff0c;那么视频转gif图是怎么制作的呢&#xff1f;可以使用视频转gif工具&#xff0c;市面上许多软件都可以完成&#xff0c;今天就给大家介绍一个视频在线转gif的方法&#xff0c;省去了下载安装的时…

LangChain手记 Question Answer 问答系统

整理并翻译自DeepLearning.AILangChain的官方课程&#xff1a;Question Answer&#xff08;源代码可见&#xff09; 本节介绍使用LangChian构建文档上的问答系统&#xff0c;可以实现给定一个PDF文档&#xff0c;询问关于文档上出现过的某个信息点&#xff0c;LLM可以给出关于该…

redis事务对比Lua脚本区别是什么

redis官方对于lua脚本的解释&#xff1a;Redis使用同一个Lua解释器来执行所有命令&#xff0c;同时&#xff0c;Redis保证以一种原子性的方式来执行脚本&#xff1a;当lua脚本在执行的时候&#xff0c;不会有其他脚本和命令同时执行&#xff0c;这种语义类似于 MULTI/EXEC。从别…

SABO-ELM电力负荷短期预测,MATLAB代码

关于电力负荷预测&#xff0c;后台留言的呼声很高。今天就为大家带来一期关于电力负荷预测的文章。 简介 简单说一下本期内容&#xff1a; ①对电力负荷数据进行处理 ②采用极限学习机(ELM)对电力负荷数据进行训练和预测 ③采用减法平均优化器算法优化极限学习机的权值阈值&…

【Linux命令详解 | du命令】 du命令用于查看文件或目录的磁盘使用情况,帮助管理存储空间。

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 基本用法2. 以人类可读的格式显示大小3. 显示总计磁盘使用量4. 包括每个文件的大小5. 限制显示的目录深度6. 排除特定文件或目录7. 指定块大小总结 简介 在Linux操作系统中&#xff0c;存储空间管理是至关重要的…

docker打包运行中的容器,生成镜像文件保存到本地

因为想着方便部署&#xff0c;将所有没问题的项目容器打包成镜像&#xff0c;走到哪儿都离线安装自动部署。 第一步先把运行中的容器打包成镜像 docker commit 运行中容器id 像打包成的镜像名称第二步将大象装进冰箱&#xff0c;不好意思说错了&#xff0c;把镜像保存到本地 …

spring框架核心技术讲解--超详细教程加案例分析

目录 一.spring简介 1.1 含义&#xff1a; 1.2 优点 1.3 官方网站&#xff1a;Spring | Why Springhttps://spring.io/why-spring 二.spring 控制反转IOC&#xff08;依赖注入&#xff09;的特点 2.1 控制反转是什么 2.2 案例&#xff08;讲解控制反转&#xff09; 三.sprin…

35_windows环境debug Nginx 源码-CLion配置CMake和启动

文章目录 生成 CMakeLists.txt 组态档35_windows环境debug Nginx 源码-CLion配置CMake和启动生成 CMakeLists.txt 组态档 修改auto目录configure文件,在 . auto/make 上边增加 . auto/cmake, 大概在 106 行。在 auto 目录下创建cmake 文件其内容如下: #!/usr/bin/env bash NG…

BLIP-Diffusion

论文大意 BLIP2 结合 Stable Diffusion&#xff0c;实现 主题驱动的图像生成与编辑。 分为两个阶段&#xff1a; 1.BLIP2 的multimodal encoder 输出与 text对齐的 visual representation&#xff1b; 2.上述生成的 特征 与 text embedding 共同引导 Stable Diffusion 的生…

Object 类的常见方法

文章目录 一、对象创建和销毁方法&#xff1a;二、对象信息获取方法&#xff1a;三、对象比较方法&#xff1a;四、线程相关方法&#xff1a;五、锁相关方法&#xff1a;六、使用案例&#xff1a;1、对象创建和销毁方法&#xff1a;2、对象信息获取方法&#xff1a;3、对象比较…

使用Python调用Java的jar包

当使用JPype时&#xff0c;你可以按照以下步骤调用Java的JAR包&#xff1a; 1. 安装JPype库&#xff1a; 你可以使用pip来安装JPype库&#xff0c;运行以下命令&#xff1a; pip install JPype1 2. 导入JPype库并启动JVM&#xff1a; 在Python中导入JPype库…

C++_深究继承

文章目录 1. 继承的概念和定义1.1 继承的概念1.2 继承定义1.2.1定义格式1.2.2 继承关系和访问限定符 2. 基类和派生类对象赋值转换3.继承中的作用域4. 派生类的默认成员函数5. 继承和友元6. 继承与静态成员7. 菱形继承即菱形虚拟继承菱形虚拟继承 8. 继承的总结与反思 1. 继承的…

scope(三)

前面两节讲了没有scoped的可以直接修改,现在讨论下把scoped这个属性去掉会是怎么样 1.准备的两个页面 放置的两个时间属性。 2.有hash值的页面 3.对比:另外的页面 可以看出只修改了当前页面的值 4.去掉scoped对比 两个页面多发生了更改,scoped对当前的页面起到一个保护的作用…

C++ primer 3.1节 课后练习

练习1.9 编写程序&#xff0c;使用while循环将50到100的整数相加。 #include <iostream> using namespace std; int main(){int i50,sum0;while (i < 100) {sum sum i;i;}cout << sum;return 0; } 练习1.10: 除了运算符将运算对象的值增加1之外&#xff0c…

mac 可以进行单片机(stm32)的开发吗?

当涉及到在Mac上进行单片机开发时&#xff0c;是完全可行的。以下是为什么Mac适合单片机开发的解释&#xff1a;开发工具&#xff1a;针对STM32单片机&#xff0c;你可以使用多种开发工具。一个常用的选择是Segger Embedded Studio&#xff0c;它是一个功能强大的集成开发环境&…

Windows系统提权(一)

权限提升概述 windows系统常见的权限&#xff1a; 用户权限 管理员权限 系统权限 访客权限 什么是提权 权限提升&#xff08;privilege escalation&#xff09;&#xff1a;攻击者通过安全漏洞把获取到的受限制的低权限用户突破限制&#xff0c;提权到高权限的管理员用户&…