1 什么是协程 coroutine
协程: 又称微线程,纤程。在一个线程内执行。 子程序: 函数,调用是通过栈来实现的。一个调用一个返回。 多线程: 避免程序顺序执行的方式之一是多线程。GIL锁只能一个点一个线程,对于io操作会有性能提升,但是依然 有线程的管理和切换,同步的开销等等 协程与一般函数的不同: 协程内部可以中断并切换,且保存当前执行状态。 协程和多线程对比的优势: 协程具有极高的执行效率,且在用户态进行,线程数量越多,性能就越好。 多进程 + 协程: 既充分利用多核,又充分发挥协程的高效率。
协程是针对单个cpu的,因为协程运行在单线程内,可以使用协程实现类似多并发的任务,并且在单cpu时,效率一般要比协程高。
在使用协程时,不由cpu来分配调度时间,时间由协程自定义。如协程遇到I/O 操作,就主动让出cpu的使用权,保存当前状态。然后等到I/O 完成,再申请使用cpu。即让编程者控制各个任务的运行顺序,从而最大限度使用cpu。
2 协程的实现
使用yield 关键字
- yield 存在的函数是一个生成器,当调用这个函数时,函数不会立即执行,而是返回一个生成器对象。使用生成器对象时,代码块才会执行。
- yield 有两个关键作用
- 返回一个值
- 接收调用方传入的值,且不影响返回值
3 例子
def consume():
r = ''
while True:
n = yield r # 断点
if not n:
return
print('消费者 正在消费: {}'.format(n))
r = '200 RMB'
def produce(c):
c.send(None) # 启动生成器
n = 0
while n < 5:
n += 1
print('生产者 正在生产: {}'.format{n})
r = c.send(n)
print('[生产者] 消费者返回: {}'.format{r})
print('------------')
c.close()
c = consume()
produce(c)
4 分析
yield 是实现生成器的重要关键字。
yiled 的三个方法及其作用, next(), send(),throw().
yield的一般形式为:temp=yield 表达式(每次迭代要返回的值)(推荐使用:既可以返回迭代的值,也可以接受send进去的参数并使用)
def my_generator(n):
for i in range(n):
temp = yield i
print(f'我是{temp}')
g=my_generator(5)
print(next(g)) #输出0, 打印结果,我是None
print(next(g)) #输出1
g.send(100) #temp的打印结果为100
print(g.send(100)) #输出2
print(next(g)) #输出3
print(next(g)) #输出4
特点:
- 延迟加载特性,生成器需要启动,如果用send启动,第一次需要传入None,否则报错
- 使用send()方法传进去的值,实际上就是yield表达式返回的值,没有传值则默认返回None
- 如果使用了send(value),传递进去的那个value会取代那个表达式的值,并且会将传递进去的那个值返回给yield表达式的结果temp,而send(value)的返回值就是原来的值
- 每次惰性返回一个值
send 方法:
send(value)是有返回值的,即迭代的那个值。
send 的主要作用, 当需要手动更改生成器里面的某一个值并且使用它,则send发送进去一个数据,然后保存到yield语句的返回值,以提供使用;send(value)的返回值就是那个本来应该被迭代出来的那个值。这样既可以保证我能够传入新的值,原来的值也不会弄丢。
throw 方法:
在生成器中抛出异常,并且这个throw函数会返回下一个要迭代的值或者是StopIteration。
def my_generator():
yield 'a'
yield 'b'
yield 'c'
yield 'd'
g=my_generator()
print(next(g))
print(next(g))
print('-------------------------')
print(g.throw(StopIteration))
print(next(g))
'''运行结果为:
a
b
-------------------------
StopIteration
触发异常之后,返回StopIteration,同时迭代终止,而StopIteration作为被传入的yield值,会顺位进入下一次迭代,即把后置位的 ‘c' 覆盖掉。
再看一个例子
def my_generator():
while True:
try:
yield 'a'
yield 'b'
yield 'c'
yield 'd'
yield 'e'
except ValueError:
print('触发“ValueError"了')
except TypeError:
print('触发“TypeError"了')
g=my_generator()
print(next(g))
print(next(g))
print('-------------------------')
print(g.throw(ValueError))
print('-------------------------')
print(next(g))
print(next(g))
print('-------------------------')
print(g.throw(TypeError))
print('-------------------------')
print(next(g))
加入while 循环之后,循环会执行完成所有调用的 yield, throw时也会执行一个yield,而throw掉的异常被捕获了,所以就不会覆盖掉 ’a‘,a顺位进入下一次迭代,这就是a在 分割线之间的原因了。
生成器的启动与 close
生成器启动时,使用next可以直接启动并调用,但是使用 send() 第一次要传入 None,不然会报错。
close() 表示关闭生成器,如果后续再调用此生成器,那么会抛出异常。
生成器的终止- StopIteration
在一个生成器中,如果没有return,则默认执行到函数完毕返回 StopIteration;
如果遇到 return,则直接抛出 StopIteration,如果 return 后面有值,会一并返回。
可以使用 except StopIteration as e 捕获异常,通过e.value 来获取值。
5 协程的状态查看
协程分别有四种状态,可以导入 inspect.getgeneratorstate() 模块来查看
GEN_CREATED: 被创建,等待执行
GEN_RUNNING: 解释器执行
GEN_SUSPENDED: 在 yield 表达式处暂停
GEN-CLOSED: 执行结束
6 协程的个人理解
从某些角度来说,协程其实就是一个可以暂停执行的函数,并且可以继续执行。那么 yield 已经可以暂停执行了,如果在暂停后有办法把一些 value 发送到暂停执行的函数中,那么这就是 Python 中的协程。
不足之处:协程函数的返回值不是特别方便获取,比如 return 的返回值需要捕获异常as e
,使用e.value来获得
Python 的生成器是协程 coroutine 的一种形式,它的局限性在于只能向它的直接调用者每次 yield一个值,这意味着那么包含 yield 的代码不能像其他代码被分离出来放到一个单独的函数中,而这正是 yield from 要解决的。