python异步io多文件_Python 异步 IO 性能又上一层楼

最近看源码的过程中,发现了一个比较有意思的库,aiomultiprocess,我认为他确实是 Python 升级到 3.8 之后一个特性的总结库,包括静态检查和性能提升。

这个是作者提供的一个 IO 性能对比图:

因为其场景主要是图片上传,所以性能对比仅供参考,从逻辑上面推理,按照作者的思路确实可以提升不少 IO 性能。

作者实际上结合了 Python 的三个特性:

multiprocess

asyncio

map/reduce

这三个库其实都是很普通的库,使用过 Python3 之后的版本,基本上都会接触到,那为什么作者可以从这三个库上面获取到比较好的性能?主要是因为他在这三个库的基础上优化了协程的调度方式。

优化后的协程调度方式更加类似于 Golang 的调度方式,唯一的差异是少了抢占式调度的思路,仅仅使用了 RoundRobin 的方式,所以 IO 性能跟 Go 还有差距,但是肯定比 Nodejs 的性能要好一些。

协程、线程、进程之间的区别

这个问题对于刚接触的人来说很复杂。先说进程和线程,进程和线程都是操作系统的概念,线程必须要在进程里面运行,另外进程是独立的存储空间,而线程的存储空间是他所在进程的一部分,所以线程的开销比进程小,线程的切换是逻辑上面的切换,但是进程的切换实际发生在物理 CPU 上面的;协程可以认为是比线程更加轻量的执行单元,他使用了线程的存储空间,协程之间通过 function 级别的 namespace 区分存储单元。综合来看他们三者最大的差异是在所在的运行时以及切换的开销。

CPU 上面的 4 核 8 线程和我们说的线程是什么关系?4 核 8 线程的意思是每个 CPU 核上面有两个独立的线程工作单元,所以 4 个核心一共有 8 个线程,这 8 个线程可以看做是 8 条流水线,我们上文讨论的线程、进程、协程全部都是操作系统逻辑上面的概念,与这个关系不大。

在一个 4 核 8 线程的机器上面,理论上可以同时运行 8 个进程。

如果进程数量大于 CPU 实际的线程数怎么办?操作系统进程有很多,开机就有 100+,那么实际上我们的 CPU 线程数是有限的,这 100+ 的系统进程需要按照不同的优先级由操作系统分时调度在不同的 CPU 线程上面工作。

如何理解并行和并发?并行和并发都是指事情同时发生,区别是...? emm 好像不是很好回答,唯一能够准确描述的是并行是独立事件,并发是关联事件。举个例子:一条四车道的路可以并排行驶 4 个车辆,但是如果这条路只有一车道的话,那么也可以行驶着四个车辆,区别是有的车辆需要排队了,同时行驶的我们认为是并行,排队走同一个车道的我们认为是并发。

当系统的进程数量 100 大于 CPU 的实际线程数量 8 时,那么系统并行数是 8,并发数量是 100。这种方法同样适用于描述线程和协程。

Python 为什么慢?

Python 解释器是单进程的。一个 Python 解释器是一个单进程的应用,再加上 GIL 锁,导致在进程里面不能并行执行线程,不同的线程是分时使用进程所在的 CPU 执行时间的。

因为 GIL 所以即使现在有充足的系统资源,那么 Request 1,Request 2,Request 3 也没有办法同时发生。

动态类型。动态类型让 Python 写起来非常的简单,所有的类型默认都是对象,所以编写代码非常的简单直接,但是执行的时候,解释器需要去推导类型,并动态检查类型有没有问题,所以导致运行时的任务比较重,相对于静态编译语言,速度跟不上。

协程的基本模型

协程实际上就是一个 function 级别的任务,分成两类,一类跟 IO 有关系,一类跟 IO 没有关系。Golang 的优势是能很好的处理计算和 IO 相关的协程,目前在 Python 里面勉强可以解决跟 IO 相关的协程。

IO 实际上是没法避免的,比如磁盘访问,网络 IO 等,他一定会有一定的等待时间,所以无论怎么调度,都会有 IO 等待,为了解决这个问题,操作系统引入了不少的模型:select、poll、epoll 等。

那么基本上所有的编程语言都是利用了系统调用,也就是使用这些模型来解决问题,但是他们只可能比操作系统差,不能更好,因为有些编程语言诞生的比较早,大规模 IO 问题当时还没遇到,说白了也就是那个时候没有那么多用户。

我使用 Python 语言大致经历了:进程 IO,线程 IO,协程 IO,协程池等,目前看到最优的就是 facebook 的这个 aiomultiprocess。

IO 协程也就是把 IO 封装成 function 级别的单元来调度,然后利用操作系统的 IO 调度模型,提升应用程序的性能,相对于进程和线程来说对于系统资源的利用更加合理,而且存储开销更加小,调度开销也小很多。

epoll 基本模型

epoll 是目前主流的 IO 调度模型,调度性能最优秀的其实是微软的 IOCP,但是因为闭源,而且系统应用开发一般集中在 linux 所以对他也了解的不多。

epoll 实际上把 IO 等待问题集中交给操作系统处理,并要求操作系统出现我关心的事件的时候提醒我(比如某个文件描述符可以读取,可以写入,出现了错误等),把 IO 问题交给操作系统之后,应用程序就可以更加集中的去处理业务问题,而操作系统有更好的方式处理 IO,这就是站在了巨人的肩膀上。

Python 中的线程,进程,协程处理 IO 问题

Python 中使用进程处理问题相当于利用多核去等待 IO,相对于线程来说,使用了多核等待,线程是单核轮训等待,使用多线程和多进程处理 IO 问题性能上面差异并不是很大,但是系统开销很大,特别的进程的开销还要大于线程,很容易导致操作系统卡顿。

协程因为开销小,所以可以同时创建上万的协程,所以其并发能力远大于进程和线程,因为总需要等待 IO 时间,所以单进程内部不同的协程之间通过共享时间片的方式也是合理的。

AsyncIO 和协程之间的关系

AsyncIO 和协程之间并没有什么关系,但是他们通常同时出现,主要是 AsyncIO 需要依附于协程作为容器,普通的协程只是在发生 IO 等待的时候交出了执行权限给其他协程,告诉其他人:我现在需要等待 IO 了,你先使用 CPU。但是在 AsyncIO 中,我们使用 epoll 配合 IO api,实际的调度是 epoll 来管理 IO,普通的协程还是利用 Python 应用程序管理 IO,所以本质上还是有很大差别的。

aiomultiprocess 为什么更快?

aiomultiprocess 将异步 IO 和多进程结合起来了,很好的利用了多核和异步 IO 的优势,前面说到 Python 是单进程的,所以即使有 AsyncIO 那么也并没有充分利用操作系统的资源,相对来说还不是很快,但是结合了多进程之后,性能就可以线性增长了。

Golang 为什么更快?

既然充分了利用了多核,那么为什么还是比 Golang 慢?主要还是调度器的原因,Golang 是抢占式调度,目前 facebook 的这个版本主要是通过 RoundRobin 调度的,默认认为每一个任务和每一个 worker 的执行时间是固定的,但是如果出现比较意外的情况,比如系统资源占用,就会导致进程出现假死,那么依附在进程队列上面的任务也不能得到调度,为此作者增加了进程的生命之周期管理 TTL,执行一定的任务之后,自毁并重新创建进程,这种方式可以缓解调度问题,但是粒度还是比较粗。

总结

aiomultiprocess 这个库可以说是很棒的一个 IO 模型库了,合理的利用多核来完成 IO 任务,相对于单独使用线程,进程,异步 IO 来说,会有一个不错的提升,可广泛用于大规模的异步 IO 处理,比如发送邮件,上传数据等。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值