本文首发于 at7h 的个人博客。

目前 Python 语言的协程从实现来说可分为两类:

  • 一种是基于传统生成器的协程,叫做 generator-based coroutines,通过包装 generator 对象实现。
  • 另一种在 Python 3.5 版本 PEP 492 诞生,叫做 native coroutines,即通过使用 async 语法来声明的协程。

本文主要介绍第二种,第一种基于生成器的协程已在 Python 3.8 中弃用,并计划在 Python 3.10 中移除。本文是「介绍」,就先不讨论太多实现原理的东西,感兴趣的童鞋可以继续关注后面的文章。

协程(coroutine)

首先,来看一个非常简单的例子:

import asyncioasync def c():await asyncio.sleep(1)return 'Done  '

这个被 async 修饰的函数 c 就是一个协程函数,该函数会返回一个协程对象

In [1]: asyncio.iscoroutinefunction(c)
Out[1]: TrueIn [2]: c()
Out[2]: <coroutine object c at 0x107b0f748>In [3]: asyncio.iscoroutine(c())
Out[3]: True

一般来说,协程函数 c 应具有以下特点:

  • 一定会返回一个协程对象,而不管其中是否有 await 表达式。
  • 函数中不能再使用 yield from
  • 函数内部可通过 await 表达式来挂起自身协程,并等待另一个协程完成直到返回结果。
  • await 表达式后面可以跟的一定是一个可等待对象,而不仅仅是协程对象。
  • 不可在 async 修饰的协程函数外使用 await 关键字,否则会引发一个 SyntaxError
  • 当对协程对象进行垃圾收集时,如果从未等待过它,则会引发一个 RuntimeWarning(如果你刚开始写 async,那你一定遇得到,不经意间就会忘掉一个 await)。

下面分别介绍下上面提到的两个概念,可等待对象协程对象

可等待对象(awaitable)

我们来看 collections.abc 模块中对 Awaitable 类的定义:

class Awaitable(metaclass=ABCMeta):__slots__ = ()@abstractmethoddef __await__(self):yield@classmethoddef __subclasshook__(cls, C):if cls is Awaitable:return _check_methods(C, "__await__")return NotImplemented

可见,可等待对象主要实现了一个 __await__ 方法。且该方法必须返回一个迭代器(iterator) ①,否则将会引发一个 TypeError

注意:主要实现是因为 await 表达式需要跟老的基于生成器的协程相兼容,即通过使用 types.coroutine()asyncio.coroutine() 装饰器返回的生成器迭代器对象(generator iterator)也属于可等待对象,但它们并未实现 __await__ 方法。

协程对象(Coroutine)

同样的,我们来看 collections.abc 模块中对 Coroutine 类的定义:

class Coroutine(Awaitable):__slots__ = ()@abstractmethoddef send(self, value):"""Send a value into the coroutine.Return next yielded value or raise StopIteration."""raise StopIteration@abstractmethoddef throw(self, typ, val=None, tb=None):"""Raise an exception in the coroutine.Return next yielded value or raise StopIteration."""if val is None:if tb is None:raise typval = typ()if tb is not None:val = val.with_traceback(tb)raise valdef close(self):"""Raise GeneratorExit inside coroutine."""try:self.throw(GeneratorExit)except (GeneratorExit, StopIteration):passelse:raise RuntimeError("coroutine ignored GeneratorExit")@classmethoddef __subclasshook__(cls, C):if cls is Coroutine:return _check_methods(C, '__await__', 'send', 'throw', 'close')return NotImplemented

由上可知,由于继承关系,协程对象是属于可等待对象的

除了协程 Coroutine 对象外,目前常见的可等待对象还有两种:asyncio.Taskasyncio.Future,下文中介绍。

协程的执行可通过调用 __await__() 并迭代其结果进行控制。当协程结束执行并返回时,迭代器会引发 StopIteration 异常,并通过该异常的 value 属性来传播协程的返回值。下面看一个简单的例子:

In [4]: c().send(None)
Out[4]: <Future pending>In [5]: async def c1():...:     return "Done  "...:
In [6]: c1().send(None)
Out[6]: StopIteration: Done  

运行

协程的运行需要在一个 EventLoop 中进行,由它来控制异步任务的注册、执行、取消等。其大致原理是:

把传入的所有异步对象(准确的说是可等待对象,如 CoroutineTask 等,见下文)都注册到这个 EventLoop 上,EventLoop 会循环执行这些异步对象,但同时只执行一个,当执行到某个对象时,如果它正在等待其他对象(I/O 处理) 返回,事件循环会暂停它的执行去执行其他的对象。当某个对象完成 I/O 处理后,下次循环到它的时候会获取其返回值然后继续向下执行。这样以来,所有的异步任务就可以协同运行。

EventLoop 接受的对象必须为可等待对象,目前主要有三种类型即 Coroutine, TaskFuture。

下面简单的介绍下 TaskFuture

  • Future 是一种特殊的低级的可等待对象,用来支持底层回调式代码与高层 async/await 式的代码交互,是对协程底层实现的封装,其表示一个异步操作的最终结果。它提供了设置和获取 Future 执行状态或结果等操作的接口。Future 实现了 __await__ 协议,并通过 __iter__ = __await__ 来兼容老式协程。一般来说,我们不需要关心这玩意儿,日常的开发也是不需要要用到它的。如有需要,就用其子类 Task。
  • Task 用来协同的调度协程以实现并发,并提供了相应的接口供我们使用。

创建一个 Task 非常简单:

In [6]: loop = asyncio.get_event_loop()
In [7]: task = loop.create_task(c())In [8]: task
Out[8]: <Task pending coro=<c() running at <ipython-input-1-3afd2bbb1944>:3>>In [9]: task.done()
Out[9]: FalseIn [10]: task.cancelled()
Out[10]: FalseIn [11]: task.result()
Out[11]: InvalidStateError: Result is not set.In [12]: await task
Out[12]: 'Done  'In [13]: task
Out[13]: <Task finished coro=<c() done, defined at <ipython-input-1-3afd2bbb1944>:3> result='Done  '>
In [14]: task.done()
Out[14]: True
In [15]: task.result()
Out[15]: 'Done  'In [16]: task = loop.create_task(c())
In [17]: task.cancel()
Out[17]: True
In [18]: await task
Out[18]: CancelledError:

上面说到,协程的运行需要在一个 EventLoop 中进行,在 Python 3.7 之前,你只能这么写 :

In [19]: loop = asyncio.get_event_loop()
In [20]: loop.run_until_complete(c())
Out[20]: 'Done  '
In [21]: loop.close()

Python 3.7 及以上版本可以直接使用 asyncio.run() :

In [22]: asyncio.run(c())
Out[22]: 'Done  '

并发

有些童鞋可能有疑问了,我写好了一个个协程函数,怎样才能并发的运行 ?

asyncio 提供了相应的两个接口:asyncio.gatherasyncio.wait 来支持:

async def c1():await asyncio.sleep(1)print('c1 done')return Trueasync def c2():await asyncio.sleep(2)print('c2 done')return Trueasync def c12_by_gather():await asyncio.gather(c1(), c2())async def c12_by_awit():await asyncio.wait([c1(), c2()])
In [23]: asyncio.run(c12_by_gather())
c1 done
c2 doneIn [24]: asyncio.run(c12_by_awit())
c1 done
c2 done

其它

上面我们介绍了 PEP 492 coroutine 的基础使用,同时 PEP 492 也相应提出了基于 async withasync for 表达式的异步上下文管理器(asynchronous context manager)和异步迭代器(asynchronous iterator)。

下面的介绍的示例将基于 Python 可迭代对象, 迭代器和生成器 里的示例展开,建议感兴趣的同学可以先看下这篇文章。

异步上下文管理器

在 Python 中,我们常会通过实现 __enter__()__exit__() 方法来实现一个上下文管理器:

In [1]: class ContextManager:...:...:     def __enter__(self):...:         print('enter...')...:...:     def __exit__(slef, exc_type, exc_val, exc_tb):...:         print('exit...')...:In [2]: with ContextManager():...:     print('Do something...')...:
enter...
Do something...
exit...

同样的,在异步编程时我们可以通过实现 __aenter__()__aexit__() 方法来实现一个上下文管理器,并通过 async with 表达式来使用。

In [1]: class AsyncContextManager:...:...:     async def __aenter__(self):...:         print('async enter...')...:...:     async def __aexit__(slef, exc_type, exc_val, exc_tb):...:         print('async exit...')...:In [2]: async with AsyncContextManager():...:     print('Do something...')...:
async enter...
Do something...
async exit...

异步迭代

在之前的文章 Python 可迭代对象, 迭代器和生成器 中,我们介绍了通过实现 __iter__() 方法来实现一个可迭代对象,通过实现迭代器协议 __iter__()__next__() 方法来实现一个迭代器对象。下面我们改造下之前的例子,实现一个异步的版本。

class AsyncLinkFinder:PATTERN = "(?<=href=").+?(?=")|(?<=href=').+?(?=')"def __init__(self, text):self.links = re.findall(self.PATTERN, text)def __aiter__(self):return AsyncLinkIiterator(self.links)class AsyncLinkIiterator:def __init__(self, links):self.links = linksself.index = 0async def _gen_link(self):try:link = self.links[self.index]self.index += 1except IndexError:link = Nonereturn linkdef __aiter__(self):return selfasync def __anext__(self):link = await self._gen_link()if link is None:raise StopAsyncIterationreturn link
In [7]: async for s in AsyncLinkFinder(TEXT):...:     print(s)
https://blog.python.org
http://feedproxy.google.com/~r/PythonSoftwareFoundationNew/~3/T3r7qZxo-xg/python-software-foundation-fellow.html
http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/lE0u-5MIUQc/why-sponsor-pycon-2020.html
http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/jAMRqiPhWSs/seeking-developers-for-paid-contract.html

例子中实现了 __aiter__() 方法的 AsyncLinkFinder 就是一个异步可迭代对象__aiter__() 方法返回的必须是一个异步迭代器,如 AsyncLinkIiterator。异步迭代器必须同时实现 __aiter__()__anext__() 方法。一个不同的点是,异步迭代中,当迭代器耗尽时,需要引发一个 StopAsyncIteration 而不是 StopIteration

同样的,我们也实现一个异步生成器版本的:

class LinkFinder:PATTERN = "(?<=href=").+?(?=")|(?<=href=').+?(?=')"def __init__(self, text):self.links = re.finditer(self.PATTERN, text)async def __aiter__(self):return (link.group() for link in self.links)

  • ① 关于迭代器的介绍可阅读 Python 可迭代对象, 迭代器和生成器。
  • ② 关于示例中 __slots__ 的介绍请查看 理解 Python 类属性之 __slots__。

参考

  • PEP 492: Coroutines with async and await syntax
  • PEP 342: Coroutines via Enhanced Generators

c++ 协程_Python3 协程(coroutine)介绍相关推荐

  1. python携程gevent_Python协程介绍以及优缺点 Greentlet和Gevent的使用

    协程,又称微线程,纤程.英文名Coroutine.协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上 ...

  2. Kotlin学习笔记26 协程part6 协程与线程的关系 Dispatchers.Unconfined 协程调试 协程上下文切换 Job详解 父子协程的关系

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 协程与线程的关系 import kotlinx.coroutines.* import java.util.concu ...

  3. Go 面试官:什么是协程,协程和线程的区别和联系?

    大家好,我是煎鱼. 最近金三银四,是面试的季节.在我的 Go 读者交流群里出现了许多小伙伴在讨论自己面试过程中所遇到的一些 Go 面试题. 今天的男主角,是工程师的必修技能,那就是 "什么是 ...

  4. 什么是协程,协程和线程的区别和联系?

    1 进程 进程是什么 进程是操作系统对一个正在运行的程序的一种抽象,进程是资源分配的最小单位. 进程在操作系统中的抽象表现 为什么有进程 为什么会有 "进程" 呢?说白了还是为了合 ...

  5. python3之协程(1)---协程简介

    原文链接:https://www.cnblogs.com/zhangxinqi/p/8337207.html 1.协程的概念 协程,又称微线程,纤程.英文名Coroutine. 线程是系统级别的它们由 ...

  6. python协成_Python协程(上)

    几个概念: event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上.当满足事件发生的时候,调用相应的协程函数. coroutine 协程:协程对象,指一个使用asy ...

  7. Kotlin学习笔记24 协程part4 协程的取消与超时

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 如何取消协程 import kotlinx.coroutines.*/*** 协程的取消*/fun main() = ...

  8. kotlin协程_Kotlin协程

    kotlin协程 In this tutorial, we'll be looking into Kotlin Coroutines. Coroutines is a vital concept si ...

  9. 王学岗Kotlin协程(三)---协程的上下文与协程的异常处理

    协程的上下文 我们使用构建器Launch去启动协程的时候,都需要指定协程上下文(没有显示指定会使用默认值). 协程上下文(CoroutineContext)是一组用于定义协程的行为元素.它由如下几项构 ...

最新文章

  1. 《告别失控:软件开发团队管理必读》一一第1章 程序员为何难以管理
  2. Python 处理字符串内置函数详解
  3. 31.CSS3变形效果【下】
  4. 尚洋优选健康美电商平台启动仪式在广州召开
  5. 几个交换问题的咨询?
  6. 数据分析学习笔记—python简单操作EXCEL
  7. 配置两个Hadoop集群Kerberos认证跨域互信(两个集群互通)
  8. .NET微信扫码支付模式二API接口开发测试
  9. Android 屏幕画笔实现
  10. 针对初学者的 MQL 5 中的自定义指标
  11. 科技赋能时代 用ocr身份证识别
  12. 【安卓手机驱动无法安装则无法连接电脑,终极100%解决方法】ADB interfacm与 Andriod安装出现黄色感叹号
  13. 动态规划之挖金矿(背包问题)
  14. 开源博客系统php 漂亮,26种基于PHP的开源博客系统
  15. leet290单词规律
  16. 模拟电路实验 03 - | 负反馈放大电路
  17. 标准模板库STL(Standard Template Library)
  18. python开发工程师岗位简介_python开发工程师是什么
  19. 大数据之hadoop
  20. java学的什么软件_java初学者用什么软件

热门文章

  1. html抓取成xml,使用XML包将html表抓取到R数据帧中
  2. 斐波那契数列大数的压位c语言,HDU 1568 Fibonacci(大数前4位)
  3. 另辟蹊径:从其他角度去解决数据库问题
  4. 墨天轮“我的DBA之路”有奖征文开始啦
  5. 开发者说:愿为你点亮“懂环境知冷暖”智能的灯
  6. 【华为云技术分享】Python大神编程常用4大工具,你用过几个?
  7. 【华为云技术分享】《跟唐老师学习云网络》—router路咋走啊
  8. 开源大数据平台HBase对接OBS操作指南
  9. edpluse怎么运行c语言,[JSP]小菜也来学Editplus+Tomcat配置jsp运行环境
  10. java 动态网页_JavaWeb01-动态网页