【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.线程池应用

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

🏆4.线程池和进程池

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

🏆5.线程池实现

概述

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

框架:

#pragma once
#include <iostream>
#include <cassert>
#include <queue>
#include <memory>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>

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_;
};

初始化多个线程

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

将threadRoutine设置为静态成员函数的原因:线程函数必须是一个接受void参数并返回void的函数,非静态成员函数隐含this指针作为第一个参数,这样会有两个参数不符合要求。

  • 为什么创建线程要将this指针传过去:
    静态成员函数内部无法调用非静态成员函数,创建线程的时候,把this指针传过去,线程函数可以访问线程对象的所有成员

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

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

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

当外部线程向任务队列中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;
 }

放入任务和取出任务

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

 //往任务队列塞任务(主线程调用)
 void push(const T &task)
 {
     lockQueue();
     taskQueue_.push(task);
     WakeUp();
     unlockQueue();
 }
 //从任务队列获取任务(线程池中的线程调用)
 T pop()
 {
     T temp = taskQueue_.front();
     taskQueue_.pop();
     return temp;
 }

封装一个任务

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

#pragma once
#include <iostream>
#include <string>

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;
};

主线程逻辑

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

#include "threadpool.hpp"
#include "test.hpp"
#include <ctime>
#include <thread>
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);
 }
}

结果:主线程是每秒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

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


  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

penguin_bark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值