Python的多任务 03 协程

易小灯塔
2017-08-07 / 0 评论 / 3,724 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年06月22日,已超过888天没有更新,若内容或图片失效,请留言反馈。

 

什么是协程

协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

通俗的讲:

在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定

协程和线程的差异

在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住

协程其实就是一个可以暂停执行的函数,并且可以恢复继续执行。那么yield已经可以暂停执行了,如果在暂停后有办法把一些 value 发回到暂停执行的函数中,那么 Python 就有了『协程』。

从技术的角度来说,“协程就是你可以暂停执行的函数”

与生成器类似,但yield 出现在表达式的右边,且产出一个表达式值作为返回值,如果yield后没有表达式,则返回None。协程可以从调用方接收数据,caller.send(params)。

1. 例子
>>> def simple(a):
    print "start:a = ", (a)
    b = yield a
    print "received b= ",(b)
    c = yield a + b
    print "received :d ", (c)

    
>>> m1 = simple(2)
>>> m1
<generator object simple at 0x0000000003D830D8>
>>> next(m1) # 激活协程, 并产出a,然后暂停
start:a =  2
2
>>> m1.send(3)
received b=  3
5
>>> m1.send(1000) # b 接收数据,并产出 a+b 的结果,然后暂停 
received :d  1000

Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    m1.send(1000)
StopIteration 

yield作为控制流程的方式来理解。

Python的多任务 03 协程

上图中 第一阶段 yield a结束,第二阶段是给b 赋值开始

再看一个例子:

def averager():
    total = 0
    count = 0
    avg = None
    while True:
        term = yield avg 
        total += term
        count +=1
        avg = total/count

ag = averager()
next(ag) # 运行到 yield avg 就停止,并返回avg值 None
ag.send(100) # item 接收到100,并运行,直到下一个yield 并停止 
2. 与协程相关的方法

我们知道了怎么创建协程,但是当执行到最后,如果不处理就会和生成器一样抛出一个stopInteration异常来终止该协程,从2.5版本开始添加了两个方法throwclose来终止协程

如果generator.thow(exception)的异常被捕获,则继续下一个yield,否则终止

3. 协程返回值

在定义体中 return value

from collections import namedTuple
Result = namedTuple("Result","count average")

def average():
      total = 0
      count = 0
      avg = None
      while True:
             term = yield
             if term is None:
                break   # 终止符
             total +=term
             count +=1
             avg = total/count
     return Result(count,avg) 

send(None) 就会break, 返回。其中Result 会作为异常的value属性返回

try:
     m2 .send(None)
exception StopInteration as exc:
     print exc.value 

应用

# BEGIN YIELD_FROM_AVERAGER
from collections import namedtuple

Result = namedtuple('Result', 'count average')


# the subgenerator
def averager():  # <1>
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # <2>
        if term is None:  # <3>
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)  # <4>


# the delegating generator
def grouper(results, key):  # <5>
    while True:  # <6>
        results[key] = yield from averager()  #生成一个使用协程的生成器,在此处暂停,等 将一个Result 赋值给对应的key


# the client code, a.k.a. the caller
def main(data):  # <8>
    results = {}
    for key, values in data.items():
        group = grouper(results, key)  # group是grouper函数生成的生成器对象
        next(group)  # 预激活协程
        for value in values:
# 把各个 value 传给 grouper。传入的值最终到达 averager 函数中 term = yield 那一行; grouper 永远不知道传入的值是什么
            group.send(value)  

# 内层循环结束后, group 实例依旧在 yield from 表达式处暂停,因此, grouper函数定义体中为 results[key] 赋值的语句还没有执行

#把 None 传入 grouper,导致当前的 averager 实例终止,也让 grouper 继续运行,再创建一个 averager 实例,处理下一组值
        group.send(None) 

    # print(results)  # uncomment to debug
    report(results)


# output report
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
              result.count, group, result.average, unit))


data = {
    'girls;kg':
        [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls;m':
        [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys;kg':
        [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys;m':
        [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}


if __name__ == '__main__':
    main(data) 

上述例子,如果子生成器不停止,委派生成器永远在yield from 处停止。

yield from iterable本质上等于for item in iterable: yield item的缩写

yield from的意义

把迭代器当作生成器使用,相当于把子生成器的定义体内联在 yield from 表达式 中。此外,子生成器可以执行 return 语句,返回一个值,而返回的值会成为 yield from 表达式的值

  1. 子生成器产出的值都直接传给委派生成器的调用方(即客户端代码将上述例子的averager 直接给了main中的group)。
  2. 使用 send() 方法发给委派生成器的值都直接传给子生成器。如果发送的值是 None,那么会调用子生成器的 __next__()方法。如果发送的值不是None,那么会 调用子生成器的 send()方法。如果调用的方法抛出 StopIteration 异常,那么委 派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。
  3. 生成器退出时,生成器(或子生成器)中的return expr表达式会触发 StopIteration(expr)异常抛出。
  4. yield from 表达式的值是子生成器终止时传给 StopIteration 异常的第一个参 数。

yield from 结构的另外两个特性与异常和终止有关。

  • 传入委派生成器的异常,除了GeneratorExit之外都传给子生成器的throw() 方 法。如果调用 throw()方法时抛出StopIteration 异常,委派生成器恢复运 行。 StopIteration 之外的异常会向上冒泡,传给委派生成器。
  • 如果把GeneratorExit 异常传入委派生成器,或者在委派生成器上调用close()方 法,那么在子生成器上调用close()方法,如果它有的话。如果调用close() 方法 导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出 GeneratorExit异常。

image-20201107151425979

0

评论 (0)

取消