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

Linux--线程池

目录

Linux线程池

线程池的概念

线程池的优点

线程池的应用场景

线程池的实现


Linux线程池

线程池的概念

线程池是一种线程的使用模式。

其存在的主要原因就为:线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

线程池的优点

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

注意:线程池可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的应用场景

线程池常见的应用场景如下:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。

相关现实场景应用:

  1. WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。
  2. 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  3. 突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

线程池的实现

下面我们用图示,表达一个简单的线程池,线程池中提供了一个任务队列,以及若干个线程(多线程)。

  • 线程池中的多个线程负责从任务队列当中拿任务,并将拿到的任务进行处理。
  • 线程池对外提供一个Push接口,用于让外部线程能够将任务Push到任务队列当中。

线程池的代码如下:

.hpp  (ThreadPool.hpp)

#include <iostream>
#include <unistd.h>
#include <queue>
#include <stdlib.h>
#include <pthread.h>class ThreadInfo
{
public:pthread_t tid_;std::string name_;
};static const int defalutnum = 5;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for(const auto& ti:threads_){if(ti.tid_ == tid){return ti.name_;}}return "None";}
public:static void *HandlerTask(void *args) // 带 static 是因为类内的函数第一个参数是默认为 this 指针{ThreadPool<T> *tp = static_cast<ThreadPool*>(args);std::string name = tp->GetThreadName(pthread_self());while(true){tp->Lock();while(tp->IsQueueEmpty()){tp->ThreadSleep();}// 分配到任务T t = tp->Pop();std::cout<< 分配到了一个任务 : " << t << "," << name << "run" << std::endl;tp->Unlock();sleep(1);// 后续的任务处理部分// run();}}void Start(){int num = defalutnum;for(int i=0; i < num; i++){threads_[i].name_ = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid_), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}
public:ThreadPool(int num = defalutnum) :threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;
};

知识回顾:为什么线程池中需要有互斥锁和条件变量?

因为线程池中的任务队列是会被多个执行流同时访问的临界资源,因此为了避免数据二义性,就会创建互斥锁与条件变量来进行保护。

线程池当中的线程要从任务队列里拿任务,前提条件是任务队列中必须要有任务,因此线程池当中的线程在拿任务之前,需要先判断任务队列当中是否有任务,若此时任务队列为空,那么该线程应该进行等待,直到任务队列中有任务时再将其唤醒,因此我们需要引入条件变量。

当外部线程向任务队列中Push一个任务后,此时可能有线程正处于等待状态,因此在新增任务后需要唤醒在条件变量下等待的线程。

注意:

  • 当某线程被唤醒时,其可能是被异常或是伪唤醒,或者是一些广播类的唤醒线程操作而导致所有线程被唤醒,使得在被唤醒的若干线程中,只有个别线程能拿到任务。此时应该让被唤醒的线程再次判断是否满足被唤醒条件,所以在判断任务队列是否为空时,应该使用while进行判断,而不是if。
  • 这里的唤醒是用的pthread_cond_signal而不是pthread_cond_broadcast,那是因为,如果外部向队列中push了一个任务,但我们却使用pthread_cond_broadcast,将所有的等待线程全部唤醒,但实际情况却仅需要一个线程,那这肯定会资源的浪费。一瞬间唤醒大量的线程可能会导致系统震荡,这叫做惊群效应。所以在唤醒线程时最好还是使用pthread_cond_signal函数唤醒一个正在等待的线程即可。
  • 当线程从任务队列中拿到任务后,该任务就已经属于当前线程了,与其他线程已经没有关系了。因此应该在解锁之后再进行处理任务,而不是在解锁之前进行。因为处理任务的过程可能会耗费一定的时间,所以我们不要将其放到临界区当中。
  • 还有就是设计的效率问题,如果我们将处理任务的操作也放在临界区来处理,那么当某一线程从任务队列中拿到任务后,其他线程还需要等待该线程将任务处理完后,才有机会进入临界区。这样的设计虽然也叫做线程池,但显然效率是远没有仅仅将拿取任务部分放在临界区的效率高。

 为什么线程池中的线程执行例程需要设置为静态方法?

使用pthread_create函数创建线程时,需要为创建的线程传入一个HandlerTask(执行例程),该HandlerTask只有一个参数类型为void*的参数,以及返回类型为void*的返回值。

而此时HandlerTask作为类的成员函数,该函数的第一个参数是隐藏的this指针,因此这里的HandlerTask函数,虽然看起来只有一个参数,而实际上它有两个参数,此时直接将该HandlerTask函数作为创建线程时的执行例程是不行的,无法通过编译。

静态成员函数属于类,而不属于某个对象,也就是说静态成员函数是没有隐藏的this指针的,因此我们需要将HandlerTask设置为静态方法,此时HandlerTask函数才真正只有一个参数类型为void*的参数。

但是在静态成员函数内部无法调用非静态成员函数,而我们需要在HandlerTask函数当中调用该类的某些非静态成员函数,比如Pop。因此我们需要在创建线程时,向Routine函数传入的当前对象的this指针,此时我们就能够通过该this指针在HandlerTask函数内部调用非静态成员函数了。

 主函数:.cpp  (proc.cc)

#include <iostream>
#include <ctime>
#include "ThreadPool.hpp"int main()
{std::cout << "process running..." << std::endl;sleep(3);ThreadPool<int> *tp = new ThreadPool<int>();tp->Start();srand(time(nullptr) ^ getpid());while(true){//1. 构建任务int x = rand() % 10 + 1;tp->Push(x);//2. 交给线程池处理std::cout << "proc thread make task: " << std::endl;sleep(1);}
}

 makefile

Threadpool:proc.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f Threadpool

 运行效果如下:

http://www.lqws.cn/news/604963.html

相关文章:

  • Node.js 使用 WebSockets 和 Socket.IO 实现实时聊天应用程序
  • 移动conda虚拟环境的安装目录
  • MAC 多应用切换技巧,单应用切换技巧
  • Adobe高阶技巧与设计师创意思维的进阶指南
  • 「日拱一码」015 机器学习常用库——scikit-learn
  • Appium与Appium Inspector配置教程
  • 埃隆・马斯克公司Neuralink 2025发布:脑机接口的跨越式突破
  • 【GNSS定位原理及算法杂记2】GNSS观测量:伪距、载波相位、多普勒频移
  • Day 24
  • 使用 Ansys Discovery 为初学者准备几何结构
  • IDS检测原理和架构
  • 分布式定时任务:xxl-job
  • CDC是什么?一文讲清CDC如何打通数据孤岛
  • linux升级降级内核实验
  • 使用 Pytorch Lightning 时追踪指标和可视化指标
  • JavaEE-博客系统项目
  • 不引入变量 异或交换的缺点
  • 模板编译原理
  • OpenLayers 入门指南:序言
  • TEXT Submitting Solutions
  • SpringBoot中RocketMQ的使用教程
  • 记一次finallshell.exe打开无法应的处理
  • CKS-CN考试之路----13
  • 多项式带余除法——线性代数题目为例
  • react调用打印机自定义样式
  • mysql语句练习
  • [CS创世SD NAND征文] 精准控制的坚固基石:CS创世SD NAND在华大HC32F4A0运动控制卡中的高可靠应用
  • React 学习(2)
  • Linux下MinIO分布式安装部署
  • 大语言模型随意猜测网址引发网络安全危机