协程初探

协程 - 廖雪峰 Python 教程

多进程与多线程均由操作系统调度,不过协程由程序员在协程的代码里显式调度。

协程没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

yield/send()

刚看廖老师协程一章的代码时是一脸懵逼,那么从 0 开始,了解 yield

10 年前的好文章: 深入理解 yield

看完再回顾廖老师的 [生产者-消费者] 模型:

def c():
	...
	n = yield r
	...

def p(c):
	...
	r = c.send(n)
	...

终于理解 yield 表达式与send() 函数两者皆可发送与反馈,协同工作,相辅相成。

send(n),p 发送了 n 给 c,c 怎么接收,yield 的返回值即是。

yield r,c 反馈了 r 给 p,p 怎么接收,send() 的返回值即是。

@asyncio.coroutine/yield from

yield from 语法由 Python 3.3 引进。

附:深入理解Python的yield from语法 ,摘抄部分代码:

# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        count += 1
        total += new_num
        average = total/count

# 委托生成器
def proxy_gen():
    while True:
        yield from average_gen()

# 调用方
def main():
    calc_average = proxy_gen()
    next(calc_average)            # 预激下生成器
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0

if __name__ == '__main__':
    main()

asyncio 是 Python 3.4 版本引入的标准库,直接内置了对异步 IO 的支持。

asyncio 提供的 @asyncio.coroutine 可以把一个 generator 标记为 coroutine 类型,然后在 coroutine 内部用 yield from 调用另一个 coroutine 实现异步操作。

使用 yield from 关键字将一个 asyncio.Future 对象向下传递给事件循环,当这个 Future 对象还未就绪时,该协程就暂时挂起以处理其他任务。一旦 Future 对象完成,事件循环将会侦测到状态变化,会将 Future 对象的结果通过 send 方法方法返回给生成器协程,然后生成器恢复工作。

import asyncio

@asyncio.coroutine
def foo():
    print('foo start')
    yield from asyncio.sleep(5)
    print('foo end')

@asyncio.coroutine
def bar():
    print('bar start')
    yield from asyncio.sleep(10)
    print('bar end')

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([foo(), bar()]))
loop.close()

# 输出结果
# bar start
# foo start
# (等了5秒的IO操作)
# foo end
#(又等了5秒的IO操作)
# bar end

async/await

Python3.5 中引入的新语法 asyncawait 可以让协程代码更简洁易读,也让协程表面上与生成器区分开。

代码仅需进行简单的替换:

import asyncio

async def foo():
    print('foo start')
    await asyncio.sleep(5)
    print('foo end')

async def bar():
    print('bar start')
    await asyncio.sleep(10)
    print('bar end')

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([foo(), bar()]))
loop.close()

另推荐一篇好文章:谈谈Python协程技术的演进