python协程详解

一、什么是协程

协程又称为微线程,协程是一种用户态的轻量级线程

协程拥有自己的寄存器和栈。协程调度切换的时候,将寄存器上下文和栈都保存到其他地方,在切换回来的时候,恢复到先前保存的寄存器上下文和栈,因此:协程能保留上一次调用状态,每次过程重入时,就相当于进入上一次调用的状态。

协程的好处:

  • 1.无需线程上下文切换的开销(还是单线程)

  • 2.无需原子操作(一个线程改一个变量,改一个变量的过程就可以称为原子操作)的锁定和同步的开销

  • 3.方便切换控制流,简化编程模型

  • 4.高并发+高扩展+低成本:一个cpu支持上万的协程都没有问题,适合用于高并发处理

缺点:

  • 1.无法利用多核的资源,协程本身是个单线程,它不能同时将单个cpu的多核用上,协程需要和进程配合才能运用到多cpu上(协程是跑在线程上的)

  • 2.进行阻塞操作时会阻塞掉整个程序:如io

二、了解协程的过程

1、yield工作原理

从语法上来看,协程和生成器类似,都是定义体中包含yield关键字的函数。

yield在协程中的用法:

  • 在协程中yield通常出现在表达式的右边,例如:datum = yield,可以产出值,也可以不产出--如果yield关键字后面没有表达式,那么生成器产出None。
  • 在协程中yield也可能从调用方接受数据,调用方是通过send(datum)的方式把数据提供给协程使用,而不是next(...)函数,通常调用方会把值推送给协程。
  • 协程可以把控制器让给中心调度程序,从而激活其他的协程。

所以总体上在协程中把yield看做是控制流程的方式。

先通过一个简单的协程的例子理解:

  1. def simple_demo():
  2. print("start")
  3. x = yield
  4. print("x:", x)
  5. sd = simple_demo()
  6. next(sd)
  7. sd.send(10)
  8. ---------------------------
  9. >>> start
  10. >>> x: 10
  11. >>> Traceback (most recent call last):
  12. >>> File "D:/python_projects/untitled3/xiecheng1.py", line 9, >>> in <module>
  13. >>> sd.send(10)
  14. >>> StopIteration

对上述例子的分析:

yield 的右边没有表达式,所以这里默认产出的值是None

刚开始先调用了next(...)是因为这个时候生成器还没有启动,没有停在yield那里,这个时候也是无法通过send发送数据。所以当我们通过next(...)激活协程后,程序就会运行到x = yield,这里有个问题我们需要注意,x = yield这个表达式的计算过程是先计算等号右边的内容,然后在进行赋值,所以当激活生成器后,程序会停在yield这里,但并没有给x赋值。

当我们调用send方法后yield会收到这个值并赋值给x,而当程序运行到协程定义体的末尾时和用生成器的时候一样会抛出StopIteration异常

如果协程没有通过next(...)激活(同样我们可以通过send(None)的方式激活),但是我们直接send,会提示如下错误:

  1. def simple_demo():
  2. print("start")
  3. x = yield
  4. print("x:", x)
  5. sd = simple_demo()
  6. # next(sd)
  7. sd.send(10)
  8. ---------------------------
  9. >>> Traceback (most recent call last):
  10. >>> File "D:/python_projects/untitled3/xiecheng1.py", line 9, >>> in <module>
  11. >>> sd.send(10)
  12. >>> TypeError: can't send non-None value to a just-started generator

关于调用next(...)函数这一步通常称为”预激(prime)“协程,即让协程向前执行到第一个yield表达式,准备好作为活跃的协程使用

协程在运行过程中有四个状态:

  • GEN_CREATE:等待开始执行
  • GEN_RUNNING:解释器正在执行,这个状态一般看不到
  • GEN_SUSPENDED:在yield表达式处暂停
  • GEN_CLOSED:执行结束

通过下面例子来查看协程的状态:

  1. >>> from inspect import getgeneratorstate
  2. >>> def simple_demo(a):
  3. print("start: a = ", a)
  4. b = yield a
  5. print("b = ", b)
  6. c = yield a + b
  7. print("c = ", c)
  8. >>> sd = simple_demo(2)
  9. >>> print(getgeneratorstate(sd))
  10. GEN_CREATED
  11. >>> next(sd) # 预激协程,使它走到第一个yield处,因为第一个yield处有yield值a,所以返回a的值,然后在此yield处阻塞
  12. start: a = 2
  13. 2
  14. >>> print(getgeneratorstate(sd))
  15. GEN_SUSPENDED
  16. >>> sd.send(3) # 发送3,进入协程接着上一次阻塞的yield处执行,yield接收参数3赋值给b,到下一个yield处返回a+b的值,然后在此yield处再次阻塞,等待下次send值
  17. b = 3
  18. 5
  19. >>> sd.send(4) # 同上一次send过程,到此结束抛异常
  20. c = 4
  21. Traceback (most recent call last):
  22. File "<pyshell#8>", line 1, in <module>
  23. sd.send(4)
  24. StopIteration
  25. >>> print(getgeneratorstate(sd))
  26. GEN_CLOSED

可以通过注释理解这个例子。

接着再通过一个计算平均值的例子来继续理解:

  1. >>> def averager():
  2. total = 0.0
  3. count = 0
  4. average = None
  5. while True:
  6. term = yield average
  7. total += term
  8. count += 1
  9. average = total/count
  10. >>> avg = averager()
  11. >>> next(avg)
  12. >>> avg.send(10)
  13. 10.0
  14. >>> avg.send(30)
  15. 20.0
  16. >>> avg.send(40)
  17. 26.666666666666668

这里是一个死循环,只要不停send值给协程,可以一直计算下去。

通过上面的几个例子我们发现,我们如果想要开始使用协程的时候必须通过next(...)方式激活协程,如果不预激,这个协程就无法使用,如果哪天在代码中遗忘了那么就出问题了,所以有一种预激协程的装饰器,可以帮助我们干这件事。

2、预激协程的装饰器

下面是预激装饰器的演示例子:

  1. from functools import wraps
  2. def coroutine(func):
  3. @wraps(func)
  4. def primer(*args,**kwargs):
  5. gen = func(*args,**kwargs)
  6. next(gen)
  7. return gen
  8. return primer
  9. @coroutine
  10. def averager():
  11. total = 0.0
  12. count = 0
  13. average = None
  14. while True:
  15. term = yield average
  16. total += term
  17. count += 1
  18. average = total/count
  19. coro_avg = averager()
  20. from inspect import getgeneratorstate
  21. print(getgeneratorstate(coro_avg))
  22. print(coro_avg.send(10))
  23. print(coro_avg.send(30))
  24. print(coro_avg.send(5))
  25. ---------------------------
  26. >>> GEN_SUSPENDED
  27. >>> 10.0
  28. >>> 20.0
  29. >>> 15.0

关于预激,在使用yield from句法调用协程的时候,会自动预激活,这样其实与我们上面定义的coroutine装饰器是不兼容的,在python3.4里面的asyncio.coroutine装饰器不会预激协程,因此兼容yield from

3、终止协程和异常处理

协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的对象)。

继续使用上面averager的例子

  1. >>> coro_avg = averager()
  2. >>> coro_avg.send(40)
  3. 40.0
  4. >>> coro_avg.send(50)
  5. 45.0
  6. >>> coro_avg.send('spam')
  7. Traceback (most recent call last):
  8. ...
  9. TypeError: unsupported operand type(s) for +=: 'float' and 'str'
  10. >>> coro_avg.send(60)
  11. Traceback (most recent call last):
  12. File "<stdin>", line 1, in <module>
  13. StopIteration

由于在协程内没有处理异常,协程会终止。如果试图重新激活协程,会抛出StopIteration 异常。

从 Python 2.5 开始,客户代码可以在生成器对象上调用两个方法:throw 和 close,显式地把异常发给协程。

  • 1:generator.throw(exc_type[, exc_value[, traceback]])

使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。

  • 2:generator.close()

使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。

示例如下:

  1. from inspect import getgeneratorstate
  2. class DemoException(Exception):
  3. """为这次演示定义的异常类型。"""
  4. pass
  5. def demo_exc_handling():
  6. print('-> coroutine started')
  7. while True:
  8. try:
  9. x = yield
  10. except DemoException:
  11. print('*** DemoException handled. Continuing...')
  12. else:
  13. print('-> coroutine received: {!r}'.format(x))
  14. raise RuntimeError('This line should never run.')
  15. >>> exc_coro = demo_exc_handling()
  16. >>> next(exc_coro)
  17. -> coroutine started
  18. >>> exc_coro.send(11)
  19. -> coroutine received: 11
  20. >>> exc_coro.send(22)
  21. -> coroutine received: 22
  22. >>> exc_coro.throw(DemoException)
  23. *** DemoException handled. Continuing...
  24. >>> getgeneratorstate(exc_coro)
  25. 'GEN_SUSPENDED'
  26. >>> exc_coro.close()
  27. >>> getgeneratorstate(exc_coro)
  28. 'GEN_CLOSED'

4、让协程返回值

在Python2中,生成器函数中的return不允许返回附带返回值。在Python3中取消了这一限制,因而允许协程可以返回值:

  1. from collections import namedtuple
  2. Result = namedtuple('Result', 'count average')
  3. def averager():
  4. total = 0.0
  5. count = 0
  6. average = None
  7. while True:
  8. term = yield
  9. if term is None:
  10. break
  11. total += term
  12. count += 1
  13. average = total/count
  14. return Result(count, average)
  15. >>> coro_avg = averager()
  16. >>> next(coro_avg)
  17. >>> coro_avg.send(10)
  18. >>> coro_avg.send(30)
  19. >>> coro_avg.send(6.5)
  20. >>> coro_avg.send(None)
  21. Traceback (most recent call last):
  22. ...
  23. StopIteration: Result(count=3, average=15.5)

发送 None 会终止循环,导致协程结束,返回结果。一如既往,生成器对象会抛出StopIteration 异常。异常对象的 value 属性保存着返回的值。

注意,return 表达式的值会偷偷传给调用方,赋值给 StopIteration 异常的一个属性。这样做有点不合常理,但是能保留生成器对象的常规行为——耗尽时抛出StopIteration 异常。如果需要接收返回值,可以这样:

  1. >>> try:
  2. ... coro_avg.send(None)
  3. ... except StopIteration as exc:
  4. ... result = exc.value
  5. ...
  6. >>> result
  7. Result(count=3, average=15.5)

获取协程的返回值要绕个圈子,可以使用Python3.3引入的yield from获取返回值。yield from 结构会在内部自动捕获 StopIteration 异常。这种处理方式与 for 循环处理 StopIteration 异常的方式一样。对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把value 属性的值变成 yield from 表达式的值。

5、yield from的使用

yield from 是 Python3.3 后新加的语言结构。在其他语言中,类似的结构使用 await 关键字,这个名称好多了,因为它传达了至关重要的一点:在生成器 gen 中使用 yield from subgen() 时,subgen 会获得控制权,把产出的值传给 gen 的调用方,即调用方可以直接控制 subgen。与此同时,gen 会阻塞,等待 subgen 终止。

yield from 可用于简化 for 循环中的 yield 表达式。例如:

  1. >>> def gen():
  2. ... for c in 'AB':
  3. ... yield c
  4. ... for i in range(1, 3):
  5. ... yield i
  6. ...
  7. >>> list(gen())
  8. ['A', 'B', 1, 2]

可以改为

  1. >>> def gen():
  2. ... yield from 'AB'
  3. ... yield from range(1, 3)
  4. ...
  5. >>> list(gen())
  6. ['A', 'B', 1, 2]

yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因此,x 可以是任何可迭代的对象。

如果 yield from 结构唯一的作用是替代产出值的嵌套 for 循环,这个结构很有可能不会添加到 Python 语言中。

yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。

PEP 380 使用了一些yield from使用的专门术语:

  • 委派生成器:包含 yield from 表达式的生成器函数;

  • 子生成器:从 yield from 表达式中 部分获取的生成器;

  • 调用方:调用委派生成器的客户端代码;

委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器,子生成器再把产出的值发给调用方。子生成器返回之后,解释器会抛出StopIteration 异常,并把返回值附加到异常对象上,此时委派生成器会恢复。

下面是一个求平均身高和体重的示例代码:

  1. from collections import namedtuple
  2. Result = namedtuple('Result', 'count average')
  3. # 子生成器
  4. def averager():
  5. total = 0.0
  6. count = 0
  7. average = None
  8. while True:
  9. # main 函数发送数据到这里
  10. print("in averager, before yield")
  11. term = yield
  12. if term is None: # 终止条件
  13. break
  14. total += term
  15. count += 1
  16. average = total/count
  17. print("in averager, return result")
  18. return Result(count, average) # 返回的Result 会成为grouper函数中yield from表达式的值
  19. # 委派生成器
  20. def grouper(results, key):
  21. # 这个循环每次都会新建一个averager 实例,每个实例都是作为协程使用的生成器对象
  22. while True:
  23. print("in grouper, before yield from averager, key is ", key)
  24. results[key] = yield from averager()
  25. print("in grouper, after yield from, key is ", key)
  26. # 调用方
  27. def main(data):
  28. results = {}
  29. for key, values in data.items():
  30. # group 是调用grouper函数得到的生成器对象
  31. group = grouper(results, key)
  32. print("\ncreate group: ", group)
  33. next(group) #预激 group 协程。
  34. print("pre active group ok")
  35. for value in values:
  36. # 把各个value传给grouper 传入的值最终到达averager函数中;
  37. # grouper并不知道传入的是什么,同时grouper实例在yield from处暂停
  38. print("send to %r value %f now"%(group, value))
  39. group.send(value)
  40. # 把None传入groupper,传入的值最终到达averager函数中,导致当前实例终止。然后继续创建下一个实例。
  41. # 如果没有group.send(None),那么averager子生成器永远不会终止,委派生成器也永远不会在此激活,也就不会为result[key]赋值
  42. print("send to %r none"%group)
  43. group.send(None)
  44. print("report result: ")
  45. report(results)
  46. # 输出报告
  47. def report(results):
  48. for key, result in sorted(results.items()):
  49. group, unit = key.split(';')
  50. print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))
  51. data = {
  52. 'girls;kg':[40, 41, 42, 43, 44, 54],
  53. 'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6],
  54. 'boys;kg':[50, 51, 62, 53, 54, 54],
  55. 'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6],
  56. }
  57. if __name__ == '__main__':
  58. main(data)

grouper 发送的每个值都会经由 yield from 处理,通过管道传给 averager 实例。grouper 会在 yield from 表达式处暂停,等待 averager 实例处理客户端发来的值。averager 实例运行完毕后,返回的值绑定到 results[key] 上。while 循环会不断创建 averager 实例,处理更多的值。

外层 for 循环重新迭代时会新建一个 grouper 实例,然后绑定到 group 变量上。前一个 grouper 实例(以及它创建的尚未终止的 averager 子生成器实例)被垃圾回收程序回收。

代码结果如下:

  1. create group: <generator object grouper at 0x7f34ce8458e0>
  2. in grouper, before yield from averager, key is girls;kg
  3. in averager, before yield
  4. pre active group ok
  5. send to <generator object grouper at 0x7f34ce8458e0> value 40.000000 now
  6. in averager, before yield
  7. send to <generator object grouper at 0x7f34ce8458e0> value 41.000000 now
  8. in averager, before yield
  9. send to <generator object grouper at 0x7f34ce8458e0> value 42.000000 now
  10. in averager, before yield
  11. send to <generator object grouper at 0x7f34ce8458e0> value 43.000000 now
  12. in averager, before yield
  13. send to <generator object grouper at 0x7f34ce8458e0> value 44.000000 now
  14. in averager, before yield
  15. send to <generator object grouper at 0x7f34ce8458e0> value 54.000000 now
  16. in averager, before yield
  17. send to <generator object grouper at 0x7f34ce8458e0> none
  18. in averager, return result
  19. in grouper, after yield from, key is girls;kg
  20. in grouper, before yield from averager, key is girls;kg
  21. in averager, before yield
  22. create group: <generator object grouper at 0x7f34ce845678>
  23. in grouper, before yield from averager, key is girls;m
  24. in averager, before yield
  25. pre active group ok
  26. send to <generator object grouper at 0x7f34ce845678> value 1.500000 now
  27. in averager, before yield
  28. send to <generator object grouper at 0x7f34ce845678> value 1.600000 now
  29. in averager, before yield
  30. send to <generator object grouper at 0x7f34ce845678> value 1.800000 now
  31. in averager, before yield
  32. send to <generator object grouper at 0x7f34ce845678> value 1.500000 now
  33. in averager, before yield
  34. send to <generator object grouper at 0x7f34ce845678> value 1.450000 now
  35. in averager, before yield
  36. send to <generator object grouper at 0x7f34ce845678> value 1.600000 now
  37. in averager, before yield
  38. send to <generator object grouper at 0x7f34ce845678> none
  39. in averager, return result
  40. in grouper, after yield from, key is girls;m
  41. in grouper, before yield from averager, key is girls;m
  42. in averager, before yield
  43. create group: <generator object grouper at 0x7f34ce845620>
  44. in grouper, before yield from averager, key is boys;kg
  45. in averager, before yield
  46. pre active group ok
  47. send to <generator object grouper at 0x7f34ce845620> value 50.000000 now
  48. in averager, before yield
  49. send to <generator object grouper at 0x7f34ce845620> value 51.000000 now
  50. in averager, before yield
  51. send to <generator object grouper at 0x7f34ce845620> value 62.000000 now
  52. in averager, before yield
  53. send to <generator object grouper at 0x7f34ce845620> value 53.000000 now
  54. in averager, before yield
  55. send to <generator object grouper at 0x7f34ce845620> value 54.000000 now
  56. in averager, before yield
  57. send to <generator object grouper at 0x7f34ce845620> value 54.000000 now
  58. in averager, before yield
  59. send to <generator object grouper at 0x7f34ce845620> none
  60. in averager, return result
  61. in grouper, after yield from, key is boys;kg
  62. in grouper, before yield from averager, key is boys;kg
  63. in averager, before yield
  64. create group: <generator object grouper at 0x7f34ce8458e0>
  65. in grouper, before yield from averager, key is boys;m
  66. in averager, before yield
  67. pre active group ok
  68. send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now
  69. in averager, before yield
  70. send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now
  71. in averager, before yield
  72. send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now
  73. in averager, before yield
  74. send to <generator object grouper at 0x7f34ce8458e0> value 1.700000 now
  75. in averager, before yield
  76. send to <generator object grouper at 0x7f34ce8458e0> value 1.550000 now
  77. in averager, before yield
  78. send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now
  79. in averager, before yield
  80. send to <generator object grouper at 0x7f34ce8458e0> none
  81. in averager, return result
  82. in grouper, after yield from, key is boys;m
  83. in grouper, before yield from averager, key is boys;m
  84. in averager, before yield
  85. report result:
  86. 6 boys averaging 54.00kg
  87. 6 boys averaging 1.68m
  88. 6 girls averaging 44.00kg
  89. 6 girls averaging 1.58m

这个试验想表明的关键一点是,如果子生成器不终止,委派生成器会在yield from 表达式处永远暂停。如果是这样,程序不会向前执行,因为 yield from(与 yield 一样)把控制权转交给客户代码(即,委派生成器的调用方)了。

6、yield from的意义

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

PEP 380 在“Proposal”一节(https://www.python.org/dev/peps/pep-0380/#proposal)分六点说明了 yield from 的行为。这里几乎原封不动地引述,不过把有歧义的“迭代器”一词都换成了“子生成器”,还做了进一步说明。上面的示例阐明了下述四点:

子生成器产出的值都直接传给委派生成器的调用方(即客户端代码);

使用 send() 方法发给委派生成器的值都直接传给子生成器。如果发送的值是None,那么会调用子生成器的 next() 方法。如果发送的值不是 None,那么会调用子生成器的 send() 方法。如果子生成器抛出 StopIteration 异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器;

生成器退出时,生成器(或子生成器)中的 return expr 表达式会触发StopIteration(expr) 异常抛出;

yield from 表达式的值是子生成器终止时传给 StopIteration 异常的第一个参数。

yield from 的具体语义很难理解,尤其是处理异常的那两点。在PEP 380 中阐述了 yield from 的语义。还使用伪代码(使用 Python 句法)演示了 yield from 的行为。

若想研究那段伪代码,最好将其简化,只涵盖 yield from 最基本且最常见的用法:yield from 出现在委派生成器中,客户端代码驱动着委派生成器,而委派生成器驱动着子生成器。为了简化涉及到的逻辑,假设客户端没有在委派生成器上调用throw(...) 或 close() 方法。而且假设子生成器不会抛出异常,而是一直运行到终止,让解释器抛出 StopIteration 异常。上面示例中的脚本就做了这些简化逻辑的假设。

下面的伪代码,等效于委派生成器中的 RESULT = yield from EXPR 语句(这里针对的是最简单的情况:不支持 .throw(...) 和 .close() 方法,而且只处理 StopIteration 异常):

  1. _i = iter(EXPR)
  2. try:
  3. _y = next(_i)
  4. except StopIteration as _e:
  5. _r = _e.value
  6. else:
  7. while 1:
  8. _s = yield _y
  9. try:
  10. _y = _i.send(_s)
  11. except StopIteration as _e:
  12. _r = _e.value
  13. break
  14. RESULT = _r

但是,现实情况要复杂一些,因为要处理客户对 throw(...) 和 close() 方法的调用,而这两个方法执行的操作必须传入子生成器。此外,子生成器可能只是纯粹的迭代器,不支持 throw(...) 和 close() 方法,因此 yield from 结构的逻辑必须处理这种情况。如果子生成器实现了这两个方法,而在子生成器内部,这两个方法都会触发异常抛出,这种情况也必须由 yield from 机制处理。调用方可能会无缘无故地让子生成器自己抛出异常,实现 yield from 结构时也必须处理这种情况。最后,为了优化,如果调用方调用 next(...) 函数或 .send(None) 方法,都要转交职责,在子生成器上调用next(...) 函数;仅当调用方发送的值不是 None 时,才使用子生成器的 .send(...) 方法。

下面的伪代码,是考虑了上述情况之后,语句:RESULT = yield from EXPR的等效代码:

  1. _i = iter(EXPR)
  2. try:
  3. _y = next(_i)
  4. except StopIteration as _e:
  5. _r = _e.value
  6. else:
  7. while 1:
  8. try:
  9. _s = yield _y
  10. except GeneratorExit as _e:
  11. try:
  12. _m = _i.close
  13. except AttributeError:
  14. pass
  15. else:
  16. _m()
  17. raise _e
  18. except BaseException as _e:
  19. _x = sys.exc_info()
  20. try:
  21. _m = _i.throw
  22. except AttributeError:
  23. raise _e
  24. else:
  25. try:
  26. _y = _m(*_x)
  27. except StopIteration as _e:
  28. _r = _e.value
  29. break
  30. else:
  31. try:
  32. if _s is None:
  33. _y = next(_i)
  34. else:
  35. _y = _i.send(_s)
  36. except StopIteration as _e:
  37. _r = _e.value
  38. break
  39. RESULT = _r

上面的伪代码中,会预激子生成器。这表明,用于自动预激的装饰器与 yield from 结构不兼容。

三、greenlet的使用

python中为实现协程封装了一些非常好用的包,首先介绍greenlet的使用。

Greenlet是python的一个C扩展,旨在提供可自行调度的‘微线程’, 即协程。generator实现的协程在yield value时只能将value返回给调用者(caller)。 而在greenlet中,target.switch(value)可以切换到指定的协程(target), 然后yield value。greenlet用switch来表示协程的切换,从一个协程切换到另一个协程需要显式指定。

以下例子:

  1. from greenlet import greenlet
  2. def test1():
  3. print(12)
  4. gr2.switch()
  5. print(34)
  6. def test2():
  7. print(56)
  8. gr1.switch()
  9. print(78)
  10. gr1 = greenlet(test1)
  11. gr2 = greenlet(test2)
  12. gr1.switch()
  13. ---------------------------
  14. >>> 12
  15. >>> 56
  16. >>> 34

当创建一个greenlet时,首先初始化一个空的栈, switch到这个栈的时候,会运行在greenlet构造时传入的函数(首先在test1中打印 12), 如果在这个函数(test1)中switch到其他协程(到了test2 打印34),那么该协程会被挂起,等到切换回来(在test2中切换回来 打印34)。当这个协程对应函数执行完毕,那么这个协程就变成dead状态。

对于greenlet,最常用的写法是 x = gr.switch(y)。 这句话的意思是切换到gr,传入参数y。当从其他协程(不一定是这个gr)切换回来的时候,将值付给x。

  1. import greenlet
  2. def test1(x, y):
  3. z = gr2.switch(x+y)
  4. print 'test1 ', z
  5. def test2(u):
  6. print 'test2 ', u
  7. gr1.switch(10)
  8. gr1 = greenlet.greenlet(test1)
  9. gr2 = greenlet.greenlet(test2)
  10. print gr1.switch("hello", " world")
  11. ---------------------------
  12. >>> 'test2 ' 'hello world'
  13. >>> 'test1 ' 10
  14. >>> None

上面的例子,第12行从main greenlet切换到了gr1,test1第3行切换到了gs2,然后gr1挂起,第8行从gr2切回gr1时,将值(10)返回值给了 z。

使用greenlet需要注意一下三点:

  • 第一:greenlet创生之后,一定要结束,不能switch出去就不回来了,否则容易造成内存泄露
  • 第二:python中每个线程都有自己的main greenlet及其对应的sub-greenlet ,不能线程之间的greenlet是不能相互切换的
  • 第三:不能存在循环引用,这个是官方文档明确说明

四、gevent的使用

gevent可以自动捕获I/O耗时操作,来自动切换协程任务。

  1. import gevent
  2. def f1():
  3. for i in range(5):
  4. print('run func: f1, index: %s ' % i)
  5. gevent.sleep(1)
  6. def f2():
  7. for i in range(5):
  8. print('run func: f2, index: %s ' % i)
  9. gevent.sleep(1)
  10. t1 = gevent.spawn(f1)
  11. t2 = gevent.spawn(f2)
  12. gevent.joinall([t1, t2])
  13. ------------------------------
  14. >>> run func: f1, index: 0
  15. >>> run func: f2, index: 0
  16. >>> run func: f1, index: 1
  17. >>> run func: f2, index: 1
  18. >>> run func: f1, index: 2
  19. >>> run func: f2, index: 2
  20. >>> run func: f1, index: 3
  21. >>> run func: f2, index: 3
  22. >>> run func: f1, index: 4
  23. >>> run func: f2, index: 4

由图中可以看出,f1和f2是交叉打印信息的,因为在代码执行的过程中,我们人为使用gevent.sleep(0)创建了一个阻塞,gevent在运行到这里时就会自动切换函数切换函数。也可以在执行的时候sleep更长时间,可以发现两个函数基本是同时运行然后各自等待。

关于协程,首先要充分理解协程的实现原理,然后使用现有的轮子greenlet和gevent时才能更加得心应手!

python协程详解的更多相关文章

  1. python协程详解,gevent asyncio

    python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...

  2. Python协程详解(二)

    上一章,我们介绍了Python的协程,并讲到用yield达到协程的效果,这一章,我们来介绍yield from的结构和作用 我们先来对比下yield和yield from的用法 def first_g ...

  3. Python协程详解(一)

    yield有两个意思,一个是生产,一个是退让,对于Python生成器的yield来说,这两个含义都成立.yield这个关键字,既可以在生成器中产生一个值,传输给调用方,同时也可以从调用方那获取一个值, ...

  4. Python进程、线程、协程详解

    进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. ...

  5. PHP协程 详解

    [开源中国] PHP 使用协同程序实现合作多任务 [风雪之隅] 在PHP中使用协程实现多任务调度

  6. Golang 之协程详解

    转自:https://www.cnblogs.com/liang1101/p/7285955.html 一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三 ...

  7. Python协程与Go协程的区别二

    写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...

  8. Python核心技术与实战——十六|Python协程

    我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...

  9. 《python开发技术详解》|百度网盘免费下载|Python开发入门篇

    <python开发技术详解>|百度网盘免费下载|Python开发入门篇 提取码:2sby  内容简介 Python是目前最流行的动态脚本语言之一.本书共27章,由浅入深.全面系统地介绍了利 ...

随机推荐

  1. memcached--add使用

    memcached是一种管理内存的软件,来动态的分配机器的内存,将需要存储的数据以key-value(键值对)的形式存储在内存中. 1.memcached使用的存储算法是hash算法在内存中存储字符串 ...

  2. docker开启2376端口CA认证及IDEA中一键部署docker项目

    嘿,大家好,今天更新的内容是docker开启2376端口CA认证及IDEA中一键部署docker项目... 先看效果 我们可以通过idea一键部署docker项目,还以通过idea的控制台实时查看容器 ...

  3. Knative 初体验:Build Hello World

    作者 | 阿里云智能事业群技术专家 冬岛 Build 模块提供了一套 Pipeline 机制.Pipeline 的每一个步骤都可以执行一个动作,这个动作可以是把源码编译成二进制.可以是编译镜像也可以是 ...

  4. 39 | 从小作坊到工厂:什么是Selenium Grid?如何搭建Selenium Grid?

  5. Google 的 Java 编码规范,参考学习!

    这份文档是 Google Java 编程风格规范的完整定义.当且仅当一个 Java 源文件符合此文档中的规则, 我们才认为它符合 Google 的 Java 编程风格. 与其它的编程风格指南一样,这里 ...

  6. UAVStack的慢SQL数据库监控功能及其实现

    UAVStack是一个全维监控与应用运维平台.UAV.Monitor具备监控功能,包含基础监控.应用/服务性能监控.日志监控.业务监控等.在应用监控中,UAV可以根据应用实例画像:其中应用实例组件可以 ...

  7. kuangbin专题 专题一 简单搜索 Dungeon Master POJ - 2251

    题目链接:https://vjudge.net/problem/POJ-2251 题意:简单的三维地图 思路:直接上代码... #include <iostream> #include & ...

  8. C语言学习书籍推荐《学通C语言的24堂课》下载

    下载地址:点我 编辑推荐 <学通C语言的24堂课>:用持续激励培养良好习惯以良好习惯铸就伟大梦想——致亲爱的读者朋友在开始学习<学通C语言的24堂课>的同时,强烈建议读者朋友同 ...

  9. 渐进式web应用开发--拥抱离线优先(三)

    _ 阅读目录 一:什么是离线优先? 二:常用的缓存模式 三:混合与匹配,创造新模式 四:规划缓存策略 五:实现缓存策略 回到顶部 一:什么是离线优先? 传统的web应用完全依赖于服务器端,比如像很早以 ...

  10. 串门赛: NOIP2016模拟赛——By Marvolo 丢脸记

    前几天liu_runda来机房颓废,顺便扔给我们一个网址,说这上面有模拟赛,让我们感兴趣的去打一打.一开始还是没打算去看一下的,但是听std说好多人都打,想了一下,还是打一打吧,打着玩,然后就丢脸了. ...