Python 协程

来源:互联网 发布:李叫兽真的是大神知乎 编辑:程序博客网 时间:2024/04/29 03:29

在协程之前我们有什么?

协程实际上不是一个新概念,作为一个并发模型,在很早以前就能看到协程的身影。只是最近才开始变得火热起来,因为它可以很好的处理IO密集型任务,而这符合互联网行业的业务需求。
在我们重新认识协程之前,先简短回顾下几个常用的并发模型。
最简单的就是串行执行的程序,遇到某一个IO事件就会阻塞直到IO事件返回,这种程序最大的缺点就是慢,耗时约等于全部IO耗时与计算耗时之和。
为了提升运行效率,可以采用多线程或多进程模型,因为IO事件实际上是不消耗计算资源的,只需要等待而已,所以在IO事件等待的时候切换到另一个任务来提升运行速度,而这需要依靠操作系统对线程和进程的调度。
但是线程和进程的分配是需要开销的,在面对大量IO事件时,系统资源就不够用了,所以才有了 select, poll, epoll等非阻塞异步IO。但是异步程序虽然性能上非常棒,但是可读性上十分反人类,因为它对原本连贯的逻辑进行了拆分,在系统规模变得很大时,我们很难理解系统的逻辑。而协程在一定程度上可以解决这个问题,它可以在保持程序的逻辑连贯性的同时,通过对任务的调度来实现调用的异步性,同时协程由于整个程序都在一个线程内,所以上下文切换的开销极小,运行效率极高。

Python 中的协程

生成器

生成器是 Python 中的一个重要特性,在 Python2 里面协程需要在生成器的基础上进行拓展。所以先来看一个生成器的例子,这个例子是一个将大文件分块读入内存的例子,它避免了文件过大无法一次性读入内存,或者是一次性读入太慢的问题。常用的 range 也有一个生成器版 xrange,range 一次性会生成所有的数据,而 xrange则到需要时才生成。

import osdef chunked_file(file, n=100):    with open(file) as fp:         while True:             chunk = fp.read(n)             if chunk:                 yield chunk.encode('utf-8')             else:                 break             fp.seek(n, os.SEEK_CUR)if __name__ == '__main__':    for chunk in chunked_file('demo.txt', 256):        print chunk                    

除了上面的用法,生成器还可以主动调用 next 方法获取下一个输出,在没有下一个输出的情况下会抛出 StopIteration , 前面的for循环只是一个语法糖帮我们处理了next和对抛出异常的检测。

>>> demo = chunked_file('demo.txt', 10)>>> demo.next()'1234567890'>>> demo.next()'1234567890'...>>> demo.next()StopIteration: 

传递数据

Python 里为生成器提供了一个 send 方法,用来向生成器发送数据。下面是一个echo函数的例子,同时可以通过调用生成器函数的close方法来关闭生成器。

# echo.pydef echo():    x = yield     while True:        x = yield x
>>> generator = echo()>>> generator.send(None) # 启动生成器>>> generator.send('hello')'hello'>>> generator.send('world')'world'>>> generator.close() # 关闭生成器

使用 yield 和 send 构建协程

有了这两个基础的语义,就可以在此基础上构建协程了。下面是一个简单的协程调度实现,通过对函数的切换达到并发执行的效果。

# -*- encoding=utf-8 -*-import Queuedef countdown(n):    while n > 0:        print '[Counting Down {n}...]'.format(n=n)        n -= 1        yielddef countup(n):    x = 0    while x < n:        print '[Counting Up {n}...]'.format(n=x)        x += 1        yielddef scheduler(fn_list=(countdown(10), countdown(5), countup(5))):    queue = Queue.deque()    queue.extend(fn_list)    while len(queue):        try:            task = queue.popleft()            task.send(None)            queue.append(task)        except StopIteration:            passif __name__ == '__main__':    scheduler()

输出结果:

[Counting Down 10...][Counting Down 5...][Counting Up 0...][Counting Down 9...][Counting Down 4...][Counting Up 1...][Counting Down 8...][Counting Down 3...][Counting Up 2...]......

参考链接

编程珠玑番外篇-Q 协程的历史,现在和未来

0 0