1 什么是协程 coroutine

  协程: 又称微线程,纤程。在一个线程内执行。 子程序: 函数,调用是通过栈来实现的。一个调用一个返回。 多线程: 避免程序顺序执行的方式之一是多线程。GIL锁只能一个点一个线程,对于io操作会有性能提升,但是依然 有线程的管理和切换,同步的开销等等 协程与一般函数的不同: 协程内部可以中断并切换,且保存当前执行状态。 协程和多线程对比的优势: 协程具有极高的执行效率,且在用户态进行,线程数量越多,性能就越好。 多进程 + 协程: 既充分利用多核,又充分发挥协程的高效率。

   协程是针对单个cpu的,因为协程运行在单线程内,可以使用协程实现类似多并发的任务,并且在单cpu时,效率一般要比协程高。

在使用协程时,不由cpu来分配调度时间,时间由协程自定义。如协程遇到I/O 操作,就主动让出cpu的使用权,保存当前状态。然后等到I/O 完成,再申请使用cpu。即让编程者控制各个任务的运行顺序,从而最大限度使用cpu。

2 协程的实现

  使用yield 关键字

  1. yield 存在的函数是一个生成器,当调用这个函数时,函数不会立即执行,而是返回一个生成器对象。使用生成器对象时,代码块才会执行。
  2. yield 有两个关键作用
  1. 返回一个值
  2. 接收调用方传入的值,且不影响返回值

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


  特点:

  1. 延迟加载特性,生成器需要启动,如果用send启动,第一次需要传入None,否则报错
  2. 使用send()方法传进去的值,实际上就是yield表达式返回的值,没有传值则默认返回None
  3. 如果使用了send(value),传递进去的那个value会取代那个表达式的值,并且会将传递进去的那个值返回给yield表达式的结果temp,而send(value)的返回值就是原来的值
  4. 每次惰性返回一个值
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 要解决的。