Python并发之异步编程框架AsyncIO简介

Posted 詩和遠方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python并发之异步编程框架AsyncIO简介相关的知识,希望对你有一定的参考价值。

在进入并发编程的世界之前,先看一个对比的例子:

举例

串行执行

import time
def count(task_name):
    print(task_name,time.strftime("%X"),"One")
    time.sleep(1) # 模拟一个需要堵塞一秒的任务
    print(task_name,time.strftime("%X"),"Two")
    
def main():
    count('Task-A')
    count('Task-B')
    count('Task-C')
    
if __name__ == "__main__":
    s = time.time()
    main()
    elapsed = time.time() - s
    print(f"Total Run Time elapsed:.2f seconds.")

运行结果:

Task-A 12:00:06 One
Task-A 12:00:07 Two
Task-B 12:00:07 One
Task-B 12:00:08 Two
Task-C 12:00:08 One
Task-C 12:00:09 Two
Total Run Time 3.04 seconds.

串行执行三次函数,每个函数sleep一秒,一共花费3秒,意料之中。

并发执行

import asyncio
import time

async def count(task_name):
    print(task_name,time.strftime("%X"),"One")
    await asyncio.sleep(1) # 模拟一个需要堵塞一秒的任务
    print(task_name,time.strftime("%X"),"Two")

async def main():
    await asyncio.gather(
        count('Task-A'), 
        count('Task-B'), 
        count('Task-C')
    )

if __name__ == "__main__":
    s = time.time()
    asyncio.run(main())
    elapsed = time.time() - s
    print(f"Total Run Time elapsed:.2f seconds.")

运行结果:

Task-A 12:18:31 One
Task-B 12:18:31 One
Task-C 12:18:31 One
Task-A 12:18:32 Two
Task-B 12:18:32 Two
Task-C 12:18:32 Two
Total Run Time 1.01 seconds.

完成了相同的任务(执行3次函数),却只用了1秒钟,这就是并发带来的好处。
还可以发现并发执行程序print出来的内容顺序和串行执行的不一样,但具体到每次调用(比如Task-A),都是Sleep了一秒。
给人的感觉就是三个函数同时执行了print(‘One’),集体sleep了一秒,然后print(‘Two’)。

相关概念

有了以上感性认识之后,我们来介绍如下术语:
并行(Parallelism):同时执行多个任务
并发(Concurrency):多个任务可以来回切换地开展,不用非得做完一个任务再做下一个
多进程:多个相互隔离的进程,每个进程有单独的地址空间,适合计算密集型任务
多线程:线程是CPU调度的最小单位,多个线程共享同一进程的资源
同步IO:CPU等待IO完成,才继续执行下一步动作,CPU利用率低
异步IO:CPU不等当前任务的IO完成,就去执行下一个任务,上一任务的IO完成后,通知CPU,CPU适时返回继续执行后续动作
阻塞:同步(sync)会造成阻塞的,导致CPU过多空闲等待
非阻塞:异步(async)不阻塞,CPU利用率高

协程

多进程实现并行资源代价昂贵,进程之间通信比较复杂,可能还要涉及到序列化和反序列化;
多线程实现并发节省资源,线程信息交换主要通过共享内存,但线程之间切换开销也比较大;
与之对应,协程(coroutine) 则是单进程、单线程的,通过任务的分解协作,达到并发的效果,属于协作式多任务(cooperative multitasking),而线程和进程都是抢占式多任务(preemptive multitasking)。
python的协程内部实现,借用了生成器(generator)的思想。

举例代码解读

上面代码中的函数定义,前面加了async之后,直接调用该函数会返回一个协程对象(coroutine object),需要被包在task或gather之后,用asyncio.run执行。

asyncio就像一个任务调度器,执行的过程中,遇到await func(x)之后,就挂起当前函数的操作,不等fun(x)执行完就去执行下一个任务,直到fun(x)结束,通知协程调度器已准备就绪,可被选择继续执行。await相当于一个可被中断的代码节点。

asyncio.gather方法就是将一系统coroutine按传入顺序打包成一个task,等待asyncio.run方法运行。
如果每个任务单独await,是不会有并发的,要放在gather中或包装成task之后执行,才支持并发。

总结与后话

AsyncIO并不是万能的,它无法取代多进程和多线程,它更适合将CPU从IO密集的任务等待中解放出来的场景,比如大量的网络请求。
如果上述举例代码将await asyncio.sleep(1)换为time.sleep(1),将无法实现并行,因为time.sleep方法是堵塞的,所以AsyncIo目前只能和部分实现了aWait机制的模块一起使用,比如:

  • aiohttp: 支持异步HTTP请求
  • aioredis: 异步 Redis IO
  • aiopg: 异步PostgreSQ请求
  • aioodbc: pyodbc的异步版本

更多可以参考aio-libs

另外,也可以用AsyncIO实现单纯的异步,比如函数A不断生产数据,函数B不断消费数据,可以用AsyncIO库每隔一段时间交替运行函数A和函数B,而不用等到A将所有数据都生产完之后,再让B去消费。这样就实现了异步调用,但总的生产数据和消费数据的时间并没有减少。

参考资料

进程、线程、协程区别
并行与并发区别
coroutines
what is coroutine anyway

开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于Python并发之异步编程框架AsyncIO简介的主要内容,如果未能解决你的问题,请参考以下文章

Python并发编程之学习异步IO框架:asyncio 中篇

python异步编程之asyncio(百万并发)

python异步编程之asyncio(百万并发)

透彻学习Python异步编程——模块asyncio之基础篇

深究Python中的asyncio库-asyncio简介与关键字

asyncio:Python异步编程模块