什么是线程池?
传统多线程方案中采用的服务器模型是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。
线程池则是一种采用预创建技术的多线程处理形式,在程序启动之初就创建一定数量线程,运行过程中将任务添加到队列,然后再将任务分配给已创建好的线程中自动启动这些任务。因为程序边运行边创建线程是比较耗时的,所以我们通过池化的思想,减少创建线程和销毁线程对程序资源的消耗。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。
线程池原理
线程池采用预创建的技术,在程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞状态,不消耗 CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中执行。当 N1 个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。
使用线程完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
线程池技术正是关注如何缩短或调整 T1,T3 时间的技术,从而提高服务器程序性能的。它把 T1,T3 分别安排在程序的启动和结束的时间段或者一些空闲的时间段,这样在程序处理多个任务时,就不会有 T1,T3 的开销了。同时,线程池不仅调整 T1 和 T3 产生的时间段,而且它还显著减少了创建线程的数目。
线程池适合场景
事实上,线程池并不是万能的。它有其特定的使用场合。线程池致力于减少线程本身的开销对应用所产生的影响,这是有前提的,前提就是线程本身开销与线程执行任务相比不可忽略。如果线程本身的开销相对于线程任务执行开销而言是可以忽略不计的,那么此时线程池所带来的好处是不明显的,比如对于FTP服务器以及Telnet服务器,通常传送文件的时间较长,开销较大,那么此时,我们采用线程池未必是理想的方法,我们可以选择“即时创建,即时销毁”的策略。
总之线程池通常适合下面的几个场合:
- 单位时间内处理任务频繁而且任务处理时间短。
- 对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。
线程池的组成部分
- 线程池管理器(ThreadPoolManager):用于创建并管理线程
- 工作线程(WorkThread):线程池中的线程
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行
- 任务队列(TaskQueue):用于存放没有处理的任务
线程池实现原理
线程池管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。而线程池一般要复用线程,所以如果是取一个 task 分配给某一个 thread,执行完之后再重新分配,在语言层面上基本都是不支持的:一般语言的 thread 都是执行一个固定的 task 函数,执行完毕线程也就结束了,因此要如何实现 task 和 thread 的分配呢?
思路就是:让每一个 thread 都去执行调度函数:循环获取一个 task,然后执行之。保证了 thread 函数的唯一性,而且复用线程执行 task。
线程池实现
线程池代码
1 |
|
测试代码
1 |
|
参考
- Progschj/ThreadPool:https://github.com/progschj/ThreadPool
- C++11线程池实现:https://blog.csdn.net/zdarks/article/details/46994607
- 线程池的原理及实现:https://blog.csdn.net/Hsuxu/article/details/8985931
- 基于c++11的100行实现简单线程池:https://blog.csdn.net/gcola007/article/details/78750220
- C++11并发学习之六:线程池的实现:https://blog.csdn.net/caoshangpa/article/details/80374651
- 使用C++11实现线程池的两种方法:https://blog.csdn.net/liushengxi_root/article/details/83932654