生成器与协程
在Python2.3中,生成器正式被纳入标准。在处理大量数据时,生成器非常有用,例如读取一个几G的文件,你没有必要将文件一次性读取到内存,而是读到一行就将其返回,这样程序不会阻塞在等待读取文件的过程中,也可以大大减少内存的占用。
生成器执行时候每次遇到yield
就会停顿一次并返回一个值,而你可以使用next()
让生成器继续执行,直到遇到下一个yield
。
后来人们意识到了,既然有办法暂停一个函数的执行,稍后让它从暂停的地方继续执行,这不就满足了协程的概念吗?然而你只能获得生成器的返回值,而不能向生成器传递值。为支持协程,PEP 342提出了你可以使用send()
向生成器发送一个值同时重启生成器的执行。
既然一个协程在执行时候可以通过yield
让出当前线程,那么我们就可以在不同的协程之间进行切换从而达到并发的效果,为此你需要编写一个任务调度器,实现基于Cooperative multitasking的任务调度,通过这个调度器管理不同协程的执行。
下面我们实现一个基于协程的并发Echo Server,这个程序的核心在于任务调度器:
- 当EchoServer在执行
accept
,recv
和send
这类I/O操作之前,会主动让出,这时控制权将移交到调度器手中。 - 当调度器发现将要执行的I/O操作是
accept
或recv
时,就将任务放到等待读取的队列,若是send
则将任务放到等待写入的队列。 - 调度器会对这两个队列进行I/O轮询,并将就绪的任务放到就绪队列中。
|
|
这段代码实现了一个小型的协作式任务调度器。这里你应该意识到协程与线程的不同点了,线程的调度是基于抢占式调度的,在抢占式调度里面,一个线程可能来不及将寄存器变量写入到内存中,或者处理器的缓存来不及同步到主内存中,就被迫切换到另一线程,这就是线程中为什么需要使用各种同步的方式来保证数据的一致性。而协程是主动让出,所以不存在多个协程同时修改同一变量的情况,也就不需要使用互斥锁等同步方式了。
而协作式调度的一个明显缺点就是,如果某个协程在执行阻塞的调用,或者执行密集的CPU计算,这时协程就无法让出,整个程序就会失去响应了。这就是为什么当今的操作系统放弃协作式调度的原因了。
异步编程
在需要处理大量I/O操作的程序中,例如爬虫系统,当程序发起一个页面请求,到接收到响应这个过程中,程序可以先执行其它的任务,而不用处于阻塞状态,这就是异步编程。异步编程可以让程序一直保持在忙碌的状态,而不用浪费时间在一些无谓的等待中。Python3.4 引入了 asyncio 模块,用于支持异步编程。asyncio要求,如果你要将一个生成器当成协程来使用,那么应该使用asyncio.coroutine
来装饰它。
这段代码的核心在于事件循环loop
,事件循环的原理类似于任务管理器。事件循环的原理并不难理解:当协程执行到yield from
的时候会暂定执行并返回一个asyncio.Future
对象给loop
,loop
内部将维持一个队列来保存这些asyncio.Future
对象,一旦loop
监测到事件发生了(例如一秒过去了),就会将相应的asyncio.Future
发送给原来的协程,这时协程将继续执行。
asyn/await 语法
Python3.5 指出,你可以使用async
将一个函数定义成协程,但async
函数中却不能出现yield
表达式。如果你要定义一个使用yield
的协程,可以使用types.coroutine
装饰器:
Python3.5 引入types.coroutine
的目的在于区分生成器和协程。为弄清楚这个区别,首先得理解什么是Awaitable Objects,Python3.5 规定,await
表达式只能用于awaitable对象。
那么,有哪些是awaitable对象呢?
- 实现了
__await__()
方法,并且这个方法返回一个可迭代的对象。 - 由
async
定义的函数,这类函数对象实现了__await__()
方法。 - 由
type.coroutine
修饰的函数。 - 由
asyncio.coroutine
修饰的函数。
现在你该明白了,由types.coroutine
修饰的函数可以用于await
表达式中,而一般的生成器则不能:
你可以将await
等同于yield from
,但与yield from
的差别是,await
的使用范围更广,任务awaitable对象都可以用于await
表达式中。
理解事件循环
让我们编写一个并发的Echo Server,这个例子的核心在于asyncio的事件循环:
|
|
David Beazley大神编写了一个事件循环Loop,用来模仿asyncio的事件循环。这个神奇的例子是不是很熟悉,很类似于我们前面的任务调度器。或许这个例子可以加深我们的理解:
|
|