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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值