线程池 | 剑出鞘,恩怨了,谁笑?
Cobb 522 1

0.1、为什么写线程池?

线程池,好东西啊,它有一池子的线程,所以叫线程池。。 为什么说它是好东西呢?有的人会觉得,那一池子线程,放在那边又不用,不浪费资源? 其实这笔账很好算的:假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。 当 T1+T3 > T2 && 这种线程被多次调度的时候,你还会觉得浪费资源吗?况且线程池内部又不是缺乏管理,相反,线程池内部管理很严格,吃白饭的线程很难有立足之地,用不上就裁员呗。

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。

线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目 看一个例子: 假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。 本段落出处

0.2、为什么写线程池?

线程的创建 pthread_creat <=> 进程的创建fork clone 系统API (都是比较重的操作) 1. 空间的切换 用户空间=>内核空间=>用户看空间 2. 实时的创建和销毁线程,在内核上要做的事情很多,如PCB的创建和销毁

那么,我们需要更加便捷的处理方式 在程序启动时,可以预先创建一组的线程缓存起来,运行过程中,如果有任务需要在单独的线程处理,可以把任务submit => 线程池 => 分配一个空闲的线程 => 执行提交的这个任务 省了在程序运行过程中,频繁的创建和销毁线程

0.3、实现思路与设计

线程池的实现思想: “管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。”

线程池的设计 phtreadpool (一个通用的框架库 => 线程池):



  1. 设想:用户可以这样来使用线程池 ThreadPool pool; pool.Start(4); // 用户可以指定线程池中线程的数量(不超过内核的数量),如果不指定,默认为1 pool.Submit(任务); // 用户会传来各种各样类型的任务,需要一个统一的类型来接收不同的任务(用基类指针) => 继承中多态调用 (基类指针 指向不同的派生类对象,调用不同派生类对象的同名覆盖方法)
  1. 在线程池中预先创建好线程,用链表存储线程thread
  1. 线程的目的是为了处理任务,用一个队列queue缓存任务Task,每次线程都要取出任务,队列出队, 0.1.多线程无法并发操作需要一把锁控制mutex任务队列,线程安全不会发生竞态条件, 0.2.如果用户提交的任务处理完了,就不可从空队列中取东西,因此,线程都要等待在某一条件下(阻塞),有东西的时候,应得到通知, ==> 条件变量cond (生产者、消费者之间的关系)
  1. 线程池中任务Task在表示的时候,Task基类类型 virtual void run() = 0; 别人使用线程池时,要从线程池库提供的抽象类继承过来,再重写run()方法,供用户自定义任务. class user:public Task {
     void run()  // 重写run方法,用户自定义任务
     {}
    }; 用Task指针指向一个new user对象,在线程池中就用基类指针Task调用run就可以了,将来发生多态,Task调用run,run是虚函数,从指向的对象先找到vfptr => vftable =>找到用户的run()地址
  1. 因此,任务队列中存放的是任务基类类型的指针,最终保存的是派生类对象的地址,进而会产生问题,指针指向的对象是用户给的,对象不一定存在(栈上的对象,出了函数就没了),存了一个栈上的无效地址,最后还用线程去执行它? 所以此处用智能指针。


0.4、代码实现

在此附上仓库地址 源码:an simple thread pool for linux

0.5、出现的问题

线程池出现的问题: 1、局部智能指针出作用域,那么强/弱智能指针都会被析构的, 这个的提交任务又是咋进行的?

解释: 0.01 线程池里边儿接收的用的是这个强智能指针,就会对资源的引用计数 +1, 外边局部的强智能指针析构了,只是把智能指针的资源的引用计数 -1, 因为这个线程池里边儿用的是强智能指针资源,不会释放。 0.02 弱智能指针呢,是不会对资源引用计数构成改变, 所以外边儿局部的强智能指针一析构,弱智能指针就提升失败了!

2、运行线程池的时候,单步调试一切正常,直接运行的时候就不出结果了。 加日志调试一下,发现弱智能指针提升强智能指针失败, 为什么会失败呢?

解释: 调试的时候是因为你代码给他打断点,让他停下来了, 如这个就看这个,就看是main函数, 里边儿你这个局部的强智能指针先析构呢, 还是线程池里边儿那个弱智能指针先提升成强智能指针,这个就看调试的时候, 还有运行的时候谁先做谁后做了, 0.01 如果说是弱智能指针先提升,那肯定能提升成功了, 因为提升成功以后会资源引用计数 +1 了啊,他就能够执行任务了。 0.02 如果是外边儿的这个局部的这个临时的这个强智能指针析构,任务不能到达线程池 那原来的这个弱智能指针就无法提升成功了呀。

评论区

索引目录