Python 协程 - 1

在协程之前我们有什么?

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

Python 中的协程

生成器

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

import os


def 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.py
def 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 Queue


def countdown(n):
    while n > 0:
        print '[Counting Down {n}...]'.format(n=n)
        n -= 1
        yield


def countup(n):
    x = 0
    while x < n:
        print '[Counting Up {n}...]'.format(n=x)
        x += 1
        yield


def 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:
            pass


if __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 协程的历史,现在和未来

猜你喜欢

转载自blog.csdn.net/preyta/article/details/71108513