python协程,从yield/send到async/await


什么是协程

python众所周知由于全局解释所GIL的限制,导致线程无法发挥多核并行计算的能力,显得比较鸡肋。既然在GIL之下,同一时刻只能有一个线程在运行,那么对于CPU密集的程序来说,线程之间的切换开销就成了拖累,而以I/O为瓶颈的程序正是协程所擅长的
参考链接:http://python.jobbole.com/86069/

协程与进程的区别

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
参考链接:https://www.zhihu.com/question/35139020

协程的优点:

  1. 无需进程上下文切换开销
  2. 无需原子锁定以及同步的开销
  3. 方便切换控制流,简化编程模型
  4. 高并发+高扩展性+低成本:一个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
  1. next(sfib)相当于sfib.send(None)
  2. 产生一个随机的秒数,作为协程中yield的返回值,而yield将b返回给fib_res

我们可以从“主”程序中控制协程计算斐波那契数列时的思考时间,协程可以返回给“主”程序计算结果
python协程,从yield/send到async/await

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)