文章目录
一、关于线程池\进程池介绍
1.1 池的概念
-
池是一组资源的集合,这组资源在程序启动时就完全被创建并初始化,这也称为为
静态资源分配
。 -
程序从系统(内存)调用和分配资源,包括之后释放资源都需要耗费大量时间。如果把相关资源放在“池“中,程序从池中获取和释放,无需动态分配,无疑速度要快很多。
-
池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。
池的概念主要目的是为了重用:让线程或进程在生命周期内可以多次使用。它减少了创建创建线程和进程的开销,“以空间换时间”,来提高了程序性能。重用不是必须的规则,但它是程序员在应用中使用池的主要原因。
1.2 池的划分
池可以分为多种,常见的有
内存池
、进程池
、线程池
和连接池
。
1.3 线程池和进程池的区别
线程池和进程池相似,用法基本一致。主要是应用场景的不同,在判断对程序是否进行线程池或进程池操作时,得先看程序业务。
业务 | 用途 | 建议使用 |
---|---|---|
IO密集型 | 读取文件,读取网络套接字频繁等。 | 线程池 |
计算密集型 | 大量消耗CPU的数学与逻辑运算,也就是我们这里说的平行计算。 | 进程池,利用硬件多核优势 |
1.5 进程池的创建(流程)
- 创建进程池,在池内放入合适数量的进程
- 将事件加入进程池的等待队列
- 使用进程池内的进程不断的执行等待事件,直到所有事件执行完毕
- 所有事件处理完毕后,关闭进程池,回收进程池
二、创建线程池\进程池的两种方法
从Python3.2python标准库为我们提供了concurrent和multiprocessing模块编写相应的异步多线程/多进程代码。
2.1 concurrent和multiprocessing区别
两个模块本质区别并不大,有的也只是调用方式略有差异。先有的 multiprocessing,后有concurrent.futures,后者的出现就是为了降低编写代码的难度,后者的学习成本较低。
本博文主要以介绍 concurrent.futures模块为主。
三、concurrent.futures模块
3.1 模块的介绍
从 Python3.2开始,Python 标准库提供了 concurrent.futures 模块,为开发人员提供了启动异步任务的高级接口。
concurrent.futures模块的基础是Exectuor,Executor是一个抽象类,它不能被直接使用。但是它提供的两个子类ThreadPoolExecutor和ProcessPoolExecutor却是非常有用,顾名思义两者分别被用来创建线程池和进程池的代码。我们可以将相应的tasks直接放入线程池/进程池,不需要维护Queue来操心死锁的问题,线程池/进程池会自动帮我们调度。
3.2 Executor.submit 创建进程/线程池
进程/线程池,只有固定个数的线程/进程,通过 max_workers 指定。
-
任务通过 Executor.submit 提交到 executor 的任务队列,返回一个 future 对象。
-
Future 是常见的一种并发设计模式。一个Future对象代表了一些尚未就绪(完成)的结果,在「将来」的某个时间就绪了之后就可以获取到这个结果。
-
任务被调度到各个 workers 中执行。但是要注意:
- 一个任务一旦被执行,在执行完毕前,会一直占用该 worker!
- 如果 workers 不够用,其他的任务会一直等待! 因此 Executor不适合实时任务。
简易创建进程池示例:
'''
Executor.submit 创建进程实例
'''
from concurrent.futures import ProcessPoolExecutor
import time, os
# 打印信息
def print_info(n):
print("%s: 开启" % os.getpid())
time.sleep(1)
return n**2
if __name__ == '__main__':
pool = ProcessPoolExecutor(4) # 开启四个进程
for i in range(10): # 执行10个任务
pool.submit(print_info, i)
四、concurrent.futures 常用模块
concurrent.futures 包含三个部分的 API:
4.1 Executor模块
也就是两个执行器的 API
-
构造器:主要的参数是 max_workers,用于指定线程池大小(或者说 workers 个数)
-
submit(fn, *args, **kwargs)