协程初探
多进程与多线程均由操作系统调度,不过协程由程序员在协程的代码里显式调度。
协程没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
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 中引入的新语法 async 和 await 可以让协程代码更简洁易读,也让协程表面上与生成器区分开。
代码仅需进行简单的替换:
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协程技术的演进