• 高效编程之 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

  • 相关阅读:
    BAT都来参加的 DevOps Master 培训
    如何快速复制BAT级的DevOps工具链
    DevOps开源工具的三种分类整理
    Devops成功的八大炫酷工具
    阿里CI/CD、DevOps、分层自动化技术
    Android爬坑之旅:软键盘挡住输入框问题的终极解决方式
    Android应用程序窗体View的创建过程
    LeetCode Convert Sorted List to Binary Search Tree
    Spark Streaming性能优化系列-怎样获得和持续使用足够的集群计算资源?
    android nfc中Ndef格式的读写
  • 原文地址:https://www.cnblogs.com/yanshw/p/11377329.html
Copyright © 2020-2023  润新知