模块 asyncio 是一个异步IO和并发框架。
asyncio 提供了协程 coroutines 创建并发应用,它使用单线程,单进程的模式进行显示的任务切换。大部分的任务切换都发生在可能会阻塞的地方,例如读取文件或者网络等等。asyncio 提供了一些特性包括在指定时间运行某个任务,指示某个 coroutines 等待其他的完成才开始执行等等。
模块 threading 和 multiprocessing 分别使用多线程和多进程进行多任务的同步运行。
概念
大多数应用程序都是线性的开发,然后依赖语言底层的线程或者进程切换任务并行运行。基于 asyncio 开发的并发程序需要在程序中手动进行上下文的切换,因为它运行在单线程,单进程的模式上。下面是需要理解的一些概念。
asyncio 框架里需要重点专注的是事件循环(event loop),它是处理事件(event)的一个主要对象,例如IO事件、系统事件、应用任务切换等等。
应用首先需要注册(register)要运行的任务到事件循环中,当得到所需的资源后,已注册的任务被事件循环唤醒执行。例如服务端程序当收到一个客户端的请求或者有数据要读取时再执行操作,当处理完成后,立刻把控制权交回给事件循环准备接受下一个事件。
控制器交回给事件循环依赖协程 coroutines,它是一个特殊的函数把控制器交回而不丢失状态,这和 yield 非常类似。事实上,在 Python 3.5 之前要想实现协程,就要使用 yield 生成器函数。asyncio 提供了基于类的抽象层,可以直接写回调方法而不用写协程。
对象 Future 是一个表示结果的数据结构,asyncio 可以监控一个 Future 对象允许应用等待一项任务完成时返回。
Future 的子类 Task 知道怎么管理一个协程的执行,Task 可以等待一个资源可用时,由事件循环调用。
协程 Coroutine
协程 Coroutine 是运行并发操作的一个语言结构,一个协程函数调用的时候就创建了一个携程对象,然后调用对象的 send() 方法就会执行它定义的代码。协程还可以使用 await 关键字暂停执行,暂停的时候不会丢失状态,然后可以等待唤醒继续执行。
运行协程
要让一个事件循环运行协程,最简单的方法是调用 run_until_complete(),参数传递一个协程对象。
执行:
本例使用 async 关键字放在函数 coroutine() 之前,代表这是一个协程函数。run_until_complete() 方法传入协程对象,开始事件循环,直到协程对象退出后返回。最后使用 try:finally 确保最后关闭事件循环。
从协程返回值
run_until_complete() 可以返回协程的结果。
执行:
协程链
一个协程可以启动另一个协程,并等待它的结果,这样更容易把一个任务分解成多个可重用的部分。下面的例子展示了必须顺序执行的两个协程,但是和其他的协程可以并发的运行。
执行:
本例在协程 worker() 中,创建了两个协程,使用关键字 await。因为控制流已经在事件循环中了,所以这里创建的两个协程也被事件循环管理。
协程调用普通函数
asyncio 在事件循环中还可以调用普通函数,如果对调用时间没有要求,方法 call_soon() 会在事件循环的下次调用函数。
call_soon() 方法的第一个参数是函数引用,第二个参数是传递给函数的参数。如果需要传递多个参数,例如关键字参数,可以使用 functools 模块的 partial() 函数。
执行:
延迟调用函数
使用方法 call_later() 延迟调用回调函数,第一个参数是要延迟的时间,单位是秒。
执行:
本例中,同样的回调函数使用不同的参数调用了多次,call_soon() 方法会使用最小的延迟时间,所以它第一个执行。
指定的时间调用函数
有时候需要在指定的时间执行回调函数。事件循环使用的时钟是 monotonic clock,而不是挂钟时间 wall time。所以为了保证时间不会倒退,应该使用事件循环的时间,因为 wall time 是可以修改的。
monotonic clock 代表某个时间点自然流逝的时间,不受 time-of-day 时钟修改的影响,例如你不想因为电脑重启而影响时间的话,就应该使用它。
wall time 通常就是我们在电脑上看到的时间,可以手动修改也包括 NTP 对它的修改。(NTP: Network Time Protocol 是用来使网络时间和本地时间同步的协议,它可以使服务器或时钟源同步修改时间)
执行: