高效编程之 concurrent.future

背景

我们知道 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

原文地址:https://www.cnblogs.com/yanshw/p/11377329.html