• Python并发方案深度对比


    本文深度对比 Python 并发方案适用场景和优缺点,主要是介绍 asyncio 这个方案。

    注:本文代码需要使用 Python 3.10 及以上版本才能正常运行。

    Python 并发和并行方案

    在 Python 世界有 3 种并发和并行方案,如下:

    1. 多线程 (threading)
    2. 多进程 (multiprocessing)
    3. 异步 IO (asyncio)

    注:并发和并行的区别先不提,最后会借着例子更好的解释,另外稍后也会提到concurrent.futures,不过它不是一种独立的方案,所以在这里没有列出来。

    这些方案是为了解决不同特点的性能瓶颈。性能问题主要有 2 种:

    1. CPU 密集型 (CPU-bound)。这也就是指计算密集型任务,它的特点事需要要进行大量的计算。例如 Python 内置对象的各种方法的执行,科学计算,视频转码等等。
    2. I/O 密集型 (I/O-bound)。凡是涉及到网络、内存访问、磁盘 I/O 等的任务都是 IO 密集型任务,这类任务的特点是 CPU 消耗很少,任务的大部分时间都在等待 I/O 操作完成。例如数据库连接、Web 服务、文件读写等等。

    如果你不知道一个任务哪种类型,我的经验是你问问自己,如果给你一个更好更快的 CPU 它可以更快,那么这就是一个 CPU 密集的任务,否则就是 I/O 密集的任务。

    这三个方案中对于 CPU 密集型的任务,优化方案只有一种,就是使用多进程充分利用多核 CPU 一起完成任务,达到提速的目的。而对于 I/O 密集型的任务,则这三种方案都可以

    接着借着一个抓取网页并写入本地 (典型的 I/O 密集型任务) 小例子来挨个拆解对比一下这些方案。先看例子:

    import requests
    
    url = 'https://movie.douban.com/top250?start='
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'  # noqa
    }
    
    
    def fetch(session, page):
        with (session.get(f'{url}{page*25}', headers=headers) as r,
              open(f'top250-{page}.html', 'w') as f):
            f.write(r.text)
    
    
    def main():
        with requests.Session() as session:
            for p in range(25):
                fetch(session, p)
    
    
    if __name__ == '__main__':
        main()
    

    在这个例子中会抓取豆瓣电影 Top250 的 25 个页面 (每页显示 10 个电影),使用 requests 库,不同页面按顺序请求,一共花了 3.9 秒:

    ➜ time python io_non_concurrent.py
    python io_non_concurrent.py  0.23s user 0.05s system 7% cpu 3.911 total
    

    这个速度虽然看起来还是很好的,一方面是豆瓣做了很好的优化,一方面我家的带宽网速也比较好。接着用上面三种方案优化看看效果。

    多进程版本

    Python 解释器使用单进程,如果服务器或者你的电脑是多核的,这么用其实是很浪费的,所以可以通过多进程提速:

    from multiprocessing import Pool
    
    def main():
        with (Pool() as pool,
              requests.Session() as session):
            pool.starmap(fetch, [(session, p) for p in range(25)])
    

    注:这里省略到了那些上面已经出现的了代码,只展示改变了的那部分。

    使用多进程池,但没指定进程数量,所以会按着 Macbook 的核数启动 10 个进程一起工作,耗时如下:

    ➜ time python use_multiprocessing.py
    python use_multiprocessing.py  2.15s user 0.30s system 232% cpu 1.023 total
    

    多进程理论上可以有十倍效率的提升,因为 10 个进程在一起执行任务。当然由于任务数量是 25,不是整数倍,是无法达到 10 倍的降低耗时,而且由于抓取太快了,没有充分显示多进程方案下的效率提升,所以用时 1 秒,也就是大约 4 倍的效率提升。

    多进程方案下没有明显的缺点,只要机器够强悍,就可以更快。

    多线程版本

    Python 解释器不是线程安全的,为此 Python 设计了 GIL: 获得 GIL 锁才可以访问线程中的 Python 对象。所以在任何一个时间,只有一个线程可以执行代码,这样就不会引发竞态条件 (Race Conditio

  • 相关阅读:
    学习记录十六 ---- spring boot
    JDK项目分析的经验分享
    [已解决]Arcpy中ERROR 001100: Failed because no statistics are available.
    电梯五方对讲接口说明 Sip五方对讲使用说明
    【Java 进阶篇】深入理解 Bootstrap 导航条与分页条
    酷开系统——酷开科技挖掘下沉市场的重要利器
    FreeSWITCH 1.10.10 简单图形化界面11 - 简单封装一下JSSIP
    数据中台避不开的话题,数据服务到底是什么?
    RocketMQ的可靠性传输
    docker 构建filebeat镜像
  • 原文地址:https://blog.csdn.net/m0_72444380/article/details/126891350