码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【Linux】第十五章 多线程(线程池)


    🏆个人主页:企鹅不叫的博客

    ​ 🌈专栏

    • C语言初阶和进阶
    • C项目
    • Leetcode刷题
    • 初阶数据结构与算法
    • C++初阶和进阶
    • 《深入理解计算机操作系统》
    • 《高质量C/C++编程》
    • Linux

    ⭐️ 博主码云gitee链接:代码仓库地址

    ⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

    💙系列文章💙


    【Linux】第一章环境搭建和配置

    【Linux】第二章常见指令和权限理解

    【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)

    【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)

    【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())

    【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)

    【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)

    【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)

    【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)

    【Linux】第十章 进程间通信(管道+system V共享内存)

    【Linux】第十一章 进程信号(概念+产生信号+阻塞信号+捕捉信号)

    【Linux】第十二章 多线程(线程概念+线程控制)

    【Linux】第十三章 多线程(线程互斥+线程安全和可重入+死锁+线程同步)

    【Linux】第十四章 多线程(生产者消费者模型+POSIX信号量)


    文章目录

    • 💙系列文章💙
    • 💎一、线程池
      • 🏆1.线程池概念
      • 🏆2.线程池价值
      • 🏆3.线程池应用
      • 🏆4.线程池和进程池
      • 🏆5.线程池实现
        • 概述
        • 初始化多个线程
        • 放入任务和取出任务
        • 封装一个任务
        • 主线程逻辑


    💎一、线程池

    🏆1.线程池概念

    线程池: 一种线程使用模式。线程过多会带来调度开销,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

    🏆2.线程池价值

    • 线程池避免了在处理短时间任务时创建与销毁线程的代价。
    • 线程池不仅能够保证内核充分利用,还能防止过分调度。

    🏆3.线程池应用

    1. 需要大量的线程来完成任务,且完成任务的时间比较短。可同时处理多任务,多请求。
    2. 有任务可以立即从线程池中调取线程取处理,节省了线程创建的时间,提高性能
    3. 有效防止服务端线程过多而导致系统过载的问题

    🏆4.线程池和进程池

    • 线程池占用资源少,但是健壮性不强
    • 进程池占用资源多,但是健壮性更强(涉及进程间通信,可利用管道的阻塞队列实现进程间同步和互斥)

    🏆5.线程池实现

    概述

    • 一个队列: 存放任务
    • 线程池中线程数: 记录线程池中创建的线程数
    • 互斥量: 互斥锁
    • 条件变量: 队列为空时的条件变量

    框架:

    #pragma once
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    #define NUM 5
    
    template <class T>
    class ThreadPool
    {
    public:
        ThreadPool(int threadNum = NUM) 
            :threadNum_(threadNum)
            ,isStart_(false)
        {
            pthread_mutex_init(&mutex_, nullptr);
            pthread_cond_init(&cond_, nullptr);
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&mutex_);
            pthread_cond_destroy(&cond_);
        }
        //加锁
        void lockQueue() 
        { 
            pthread_mutex_lock(&mutex_); 
        }
        //解锁
        void unlockQueue() 
        { 
            pthread_mutex_unlock(&mutex_); 
        }
        //判空
        bool IsEmpty()
        {
            return taskQueue_.empty(); 
        }
        //等待
        void Wait() 
        { 
            pthread_cond_wait(&cond_, &mutex_);
        }
        //唤醒
        void WakeUp() 
        { 
            pthread_cond_signal(&cond_); 
        }
    private:
        bool isStart_;          //线程池是否开始
        int threadNum_;         //线程池中线程的数量
        queue<T> taskQueue_;    //任务队列
        pthread_mutex_t mutex_;
        pthread_cond_t cond_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    初始化多个线程

    • 线程池中,创建多个线程需要设置静态方法:

    原本threadRoutine函数参数只有一个void*类型

    创建线程的时候,把this指针传过去,让启动函数的arg 参数去接收即可作为成员函数,第一个参数默认是this指针,编译无法通过。

    静态成员函数属于类,但不属于某个对象,静态成员函数没有this指针,所以将threadRoutine设置成静态函数,只有一个参数void*。

    静态成员函数内部无法调用非静态成员函数,创建线程的时候,把this指针传过去,让启动函数的args 参数去接收即可

    • 线程池中互斥锁和条件变量作用:

    线程池中的任务队列是会被多个执行流同时访问的临界资源,因此我们需要引入互斥锁对任务队列进行保护。

    线程池中的线程拿任务的时候判断线程池中是否有任务,如果队列此时没有任务,则阻塞等待,直到有任务被唤醒,需要引入条件变量

    当外部线程向任务队列中Push一个任务后,需要唤醒等待的线程。

    • 使用while:

    防止某个线程出现异常导致影响其他线程

     //成员函数,都有默认参数this,而static函数没有this指针
     static void *threadRoutine(void *args)
     {
         pthread_detach(pthread_self());
         ThreadPool<T> *tp = (ThreadPool<T> *)args;
         while (1)
         {
             tp->lockQueue();
             while (tp->IsEmpty())
             {
                 tp->Wait();
             }
             //拿任务
             T t = tp->pop();
             tp->unlockQueue();
             // 解锁后处理任务
             int x, y;
             char oper;
             t.get(&x, &y, &oper);
             cout << "新线程完成计算任务: " << x << oper << y << "=" << t.run() << endl;
         }
     }
     void start()
     {
         assert(!isStart_);
         for (int i = 0; i < threadNum_; i++)
         {
             pthread_t temp;
             pthread_create(&temp, nullptr, threadRoutine, this);//注意参数传入this指针
         }
         isStart_ = true;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    放入任务和取出任务

    放任务: 主线程往任务队列中放任务,放任务之前进行加锁,放完任务解锁,然后唤醒在条件变量下等待的队列
    取任务: 线程池中的线程从任务队列中取任务,这里不需要加锁,因为这个动作在启动函数中加锁的那一段区间中被调用的,其实已经上锁了

     //往任务队列塞任务(主线程调用)
     void push(const T &task)
     {
         lockQueue();
         taskQueue_.push(task);
         WakeUp();
         unlockQueue();
     }
     //从任务队列获取任务(线程池中的线程调用)
     T pop()
     {
         T temp = taskQueue_.front();
         taskQueue_.pop();
         return temp;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    封装一个任务

    实现一个任务类,生产者把这个任务放进阻塞队列中,消费者取出并进行处理。其中还有一个run的任务执行方法

    #pragma once
    #include 
    #include 
    
    using namespace std;
    
    class Task
    {
    public:
     Task()
         : _x(0), _y(0), _op('?')
     {
     }
    
     Task(int x, int y, char op)
         : _x(x), _y(y), _op(op)
     {
     }
    
     ~Task()
     {
     }
    
     int operator()()
     {
         return run();
     }
     int run()
     {
         int result = 0;
         switch (_op)
         {
         case '+':
             result = _x + _y;
             break;
         case '-':
             result = _x - _y;
             break;
         case '*':
             result = _x * _y;
             break;
         case '/':
         {
             if (_y == 0)
             {
                 cout << "div zero, abort" << endl;
                 result = -1;
             }
             else
             {
                 result = _x / _y;
             }
         }
         break;
         case '%':
         {
             if (_y == 0)
             {
                 cout << "mod zero, abort" << endl;
                 result = -1;
             }
             else
             {
                 result = _x % _y;
             }
         }
         break;
         default:
             cout << "非法操作: " << _op << endl;
             break;
         }
         return result;
     }
     int get(int *e1, int *e2, char *op)
     {
         *e1 = _x;
         *e2 = _y;
         *op = _op;
     }
    
    private:
     int _x;
     int _y;
     char _op;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    主线程逻辑

    主线程负责创建线程池,然后放任务到线程池即可

    #include "threadpool.hpp"
    #include "test.hpp"
    #include 
    #include 
    using namespace std;
    const string operators = "+/*/%";
    int main()
    {
     srand((unsigned int)time(nullptr));
     ThreadPool<Task> *tp = new ThreadPool<Task>;
     tp->start();
    
     // 派发任务的线程
     while (true)
     {
         int x = rand() % 50;
         int y = rand() % 10;
         char oper = operators[rand() % operators.size()];
         cout << "主线程派发计算任务: " << x << oper << y << "=?" << endl;
         Task t(x, y, oper);
         tp->push(t);
         sleep(1);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    结果:主线程是每秒Push一个任务,线程池就处理一个任务

    [Jungle@VM-20-8-centos:~/lesson36/threadpool]$ ./main
    主线程派发计算任务: 40%3=?
    新线程完成计算任务: 40%3=1
    主线程派发计算任务: 25+4=?
    新线程完成计算任务: 25+4=29
    主线程派发计算任务: 38/4=?
    新线程完成计算任务: 38/4=9
    主线程派发计算任务: 30%8=?
    新线程完成计算任务: 30%8=6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意: 如果想让线程池处理其他不同的任务请求时,只需要提供一个任务类,在该任务类当中提供对应的任务处理方法就行了。


  • 相关阅读:
    244:vue+openlayers 显示滚动效果的线段Line
    2.4GHz无线MCU芯片:CSM2433芯片 亿胜盈科
    八、Nacos配置管理(统一配置管理、配置热更新、配置共享)
    校企联动,促进就业丨四川文化产业职业学院领导一行莅临我司调研交流
    【Java】控制语句学习笔记
    【C++】详解 void*
    verdi dump状态机的波形时直接显示状态名
    javaScript 内存管理机制
    windows下flutter的环境安装
    【C++笔记】模板进阶
  • 原文地址:https://blog.csdn.net/YQ20210216/article/details/128005394
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号