python协程,从yield/send到async/await
什么是协程
python众所周知由于全局解释所GIL的限制,导致线程无法发挥多核并行计算的能力,显得比较鸡肋。既然在GIL之下,同一时刻只能有一个线程在运行,那么对于CPU密集的程序来说,线程之间的切换开销就成了拖累,而以I/O为瓶颈的程序正是协程所擅长的
参考链接:http://python.jobbole.com/86069/
协程与进程的区别
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
参考链接:https://www.zhihu.com/question/35139020
协程的优点:
- 无需进程上下文切换开销
- 无需原子锁定以及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
协程的缺点
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
从yield说起
普通的创建斐波拉契的代码:
def old_fib(n):
res = [0] * n
index = 0
a = 0
b = 1
while index < n:
res[index] = b
a, b = b, a + b
index += 1
return res
print('-'*10 + 'test old fib' + '-'*10)
for fib_res in old_fib(20):
print(fib_res)
但是如果我们仅仅只需要拿到第m位的斐波拉契,这样创建起来就很浪费内存。
这个时候yield就派上用场了。
def fib(n):
index = 0
a = 0
b = 1
while index < n:
yield b
a, b = b, a + b
index += 1
print('-'*10 + 'test yield fib' + '-'*10)
for fib_res in fib(20):
print(fib_res)
当一个函数中包含yield语句时,python会自动将其识别为一个生成器。这时fib(20)并不会真正调用函数体,而是以函数体生成了一个生成器对象实例。
生成器有一个延迟操作。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。
在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行。
yield在这里可以保留fib函数的计算现场,暂停fib的计算并将b返回。而将fib放入for…in循环中时,每次循环都会调用next(fib(20)),唤醒生成器,执行到下一个yield语句处,直到抛出StopIteration异常。此异常会被for循环捕获,导致跳出循环。
send来了
send可以给yield发送返回值
import random
import time
def stupid_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_cnt = yield b
print('let me think {0} secs'.format(sleep_cnt))
time.sleep(sleep_cnt)
a, b = b, a + b
index += 1
print('-'*10 + 'test yield send' + '-'*10)
N = 20
sfib = stupid_fib(N)
fib_res = next(sfib) #相当于sfib.send(None)
while True:
print(fib_res)
try:
fib_res = sfib.send(random.uniform(0, 0.5))
except StopIteration:
break
- next(sfib)相当于sfib.send(None)
- 产生一个随机的秒数,作为协程中yield的返回值,而yield将b返回给fib_res
我们可以从“主”程序中控制协程计算斐波那契数列时的思考时间,协程可以返回给“主”程序计算结果
yield from介绍
yield用于重构生成器。
def copy_fib(n):
print('I am copy from fib')
yield from fib(n)
print('Copy end')
print('-'*10 + 'test yield from' + '-'*10)
for fib_res in copy_fib(20):
print(fib_res)