背景
我们知道 Python 中有多线程threading 和多进程multiprocessing 实现并发,
但是这两个东西开销很大,一是开启线程/进程的开销,二是主程序和子程序之间的通信需要 序列化和反序列化,
所以有些时候需要使用更加高级的用法,然而这些高级用法十分复杂,而且 threading 和 multiprocessing 用法还不一样。
于是诞生了 concurrent.future
1. 它可以解决大部分的复杂问题 【但并不是全部,如果尝试后效果不好,还需要使用他们的高级用法】
2. 而且统一了线程和进程的用法
concurrent.future 提供了 ThreadPoolExecutor 和 ProcessPoolExecutor 两个类,其实是对 线程池和进程池 的进一步抽象,而且具有以下特点:
3. 主程序可以获取子程序的状态和返回值
4. 子程序完成时,主程序能立刻知道
效率验证
求最大公约数,测试数据如下
def gcd(pair): # 最大公约数 a, b = pair low = min(a, b) for i in range(low, 0, -1): if a % i == 0 and b % i == 0: return i numbers = [(1963309, 2265973), (2030677, 3814172), (1551645, 2229620), (2039045, 2020802)]
无并发
sum = 0 for i in range(20): start = time.time() results = list(map(gcd, numbers)) end = time.time() sum += end - start print(sum/20) # 0.6637879729270935
多线程
from concurrent.futures import ThreadPoolExecutor sum = 0 for i in range(20): start = time.time() pool = ThreadPoolExecutor(max_workers=3) results = list(pool.map(gcd, numbers)) end = time.time() sum += end - start print(sum/20) # 0.9184025406837464
分析:由于全局解释器锁GIL的存在,多线程无法利用多核CPU进行并行计算,而是只使用了一个核,加上本身的开销,计算效率更低了。
通过 资源管理器 查看 CPU 使用率:25%左右 【4核,用了一个】
多进程
from concurrent.futures import ProcessPoolExecutor if __name__ == '__main__': sum = 0 for i in range(20): start = time.time() pool = ProcessPoolExecutor(max_workers=3) results = list(pool.map(gcd, numbers)) end = time.time() sum += end - start print(sum/20) # 0.8655495047569275
分析:利用多核CPU并行计算,比多线程快了点,但是由于本身的开销,还是没有无并发效率高,
通过 资源管理器 查看 CPU 使用率:75%左右 【4核,用了三个,max_workers=3】
这主要是数据量太小了,体现不出并发的优势,于是我把数据量稍微加大点
numbers = [(1963309, 2265973), (2030677, 3814172), (1551645, 2229620), (2039045, 2020802)] * 10
重新测试,无并发 7s,多进程 2s,效果明显提高。
注意,在使用多进程时,必须把 多进程代码 写在 if __name__ == '__main__' 下面,否则异常,甚至报错
concurrent.futures.process.BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.
小结:多线程不适合计算密集型,适合IO密集型,后面我会验证,多进程适合计算密集型。
API 用法
具体方法参照参考资料,非常简单,这里我就不写了。
参考资料:
https://www.jianshu.com/p/b9b3d66aa0be