生成器与协程
在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计算,这时协程就无法让出,整个程序就会失去响应了。这就是为什么当今的操作系统放弃协作式调度的原因了。