此文已由作者张耕源授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

在最近发布的 Python 3.5 版本中,官方正式引入了 async/await关键字、在 asyncio [1] 标准库中实现了IO多路复用、原生协程(coroutine)与 事件循环(event loop),让人耳目一新,本文也尝试对 Python 3.5 新增加的原生协程 机制与asyncio标准库相关的内容做一个小结。

IO多路复用与协程的引入,可以极大的提高高负载下程序的IO性能表现。几年前,M$在 C# 中便已经通过引入 async/await 关键字实现了一套异步IO机制,成为业界模范, Python现在引入相同的编程范式也算博众家之长。

事实上,在官方实现原生协程机制前,在目前比较流行的 Python 2.X 版本中,由于 GIL [2] 的存在,Python 程序的多线程/多进程性能非常不理想,使得协程成为 Python 并发编程的最佳模型,大量的 Python 项目都开始通过使用第三方库实现的协程编写程序 (如 eventlet / gevent )、特别是网络编程相关的 Python 项目。不过限于 Python 2 语言实现的局限,协程的实现比较原始,众多第三方库的实现并不统一,并且通常都需要 使用一些特殊的编程技巧(monkey patch / green 标准库等手段)才能实现非阻塞IO等特 性来真正提高性能。

相比之下,直接官方提供标准库原生支持"async io"与协程的 Python 3.5 编写程序无疑 更加方便。

async/await

官方在 PEP-492 [3] 中定义了 async/await 关键字来使用协程。

声明一个协程非常简单,通过 async 实现:

async def foo():pass

在普通的函数声明前加上async关键字,这个函数就变成了一个协程。

需要注意的是,在 async def 定义的协程内,不能含有 yield 或 yield from 表达式,否则会报 SyntaxError 异常。

await表示等待另一个协程执行完成返回,必须在协程内才能使用。

下面是一个官方文档中提供的协程简单示例,非常直观的表示了 async/await、协程 与事件循环的执行过程:

import asyncioasync def compute(x, y):print("Compute %s + %s ..." % (x, y))await asyncio.sleep(1.0)    return x + yasync def print_sum(x, y):result = await compute(x, y)print("%s + %s = %s" % (x, y, result))loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

从图中可以看到,asyncio 标准库自带的事件循环负责所有协程的调度。在首先执行 print_sum 协程时,内部使用await等待另外一个协程compute的返回结果,于是本地 协程被挂起去执行协程compute。而协程compute执行过程中调用了 await asyncio.sleep(1.0),于是本地协程挂起1秒、将程序控制权交回事件循环,1秒 后再恢复协程compute的执行直到整个stack执行结束。

而在大量协程并发执行的过程中,除了在协程中主动使用 await,当本地协程发生 IO等待、调用 asyncio.sleep()等方法时,程序的控制权也会在不同的协程间切换,从 而在 GIL 的限制下实现最大程度的并发执行,不会由于等待IO等原因导致程序阻塞,达到 较高的性能表现。

值得一提的是,asyncio 和类似 libev 一样的众多第三方类库实现的事件循环一样, 在不同平台上会使用不同的轮询机制,比如在Linux平台上使用 poll/epoll、BSD平台上 使用 kqueue、NT内核上会直接使用Proactor模式的完成端口。

yield from

如果有一直关注 Python 3 原生协程实现的同学,应该会知道其实它是靠生成器 (Generator)实现的。yield from 则是在 PEP-380 [4] 中新增加的生成器定义 关键字,与原生协程的实现密不可分。

原理上 yield from 基本等价于 await,只是 await 针对协程的实现做了更多具体 的处理与约定。

yield from 表达式的语义定义有很长的一串,要彻底搞明白它的实现,需要先学习在 PEP-342 [5] 中描述的增强型生成器(Enhanced Generators),但这里我们可以简单的把 它看作一个生成器语法糖:

for v in g:    yield v

加上在生成器内

return value

等价于

raise StopIteration(value)

这样实现以后,像这样的表达式

y = f(x)

我们就可以用 yield from 语法将 f(x) 改造成协程实现了

y = yield from g(x)

g 是 f 的生成器,只需要保证两者最终返回值一致,我们并不关心生成器 g 的中间 状态。

如果要详细了解这里的实现,建议还是阅读 PEP-380 与 PEP-342 原文,里面有专门的 章节专门描述这个问题。

context manager

PEP-492 中让人眼前一亮的一点是定义了协程的上下文管理器(context manager),新增 了 async with 语法,让我们可以将一个上下文作为协程处理,在进入(enter)和退出 (exit)一个 BLOCK 时做协程调度操作:

async with EXPR as VAR:BLOCK

与普通的 context manager 相比,async 版本的上下文管理器在内部方法前加了字母 "a"

class AsyncContextManager:async def __aenter__(self):await log('entering context')async def __aexit__(self, exc_type, exc, tb):await log('exiting context')

一种典型的应用就是进入临界区

class Lock:async def __aenter__(self):await self.lock.lock()async def __aexit__(self, exc_type, exc, tb):await self.lock.release()async with Lock():...

类似的语法还有 async for

async for TARGET in ITER:BLOCK

这里不再赘述。

promise/future

promise 是一种最近在 nodejs 流行起来另外一种异步编程范式,在不同的地方可能也 被称作 future / deferred,但一般都指的是同一种类似的东西。promise 并没有 一个非常官方的标准,我了解的比较知名的promise标准规范有 Promises A+ [6]。 Python 从 Python 3.4 开始也提供了 asyncio.Future 实现类似的功能。

promise 的核心思想是为一个异步操作定义操作成功和失败的不通情况下的的回调 函数。在 nodejs 这类缺少官方 await 语法支持的语言中,能有效减轻 callback hell 问题,让代码更简洁,减少 raw callback 写法导致的缩进太多的 问题,并能方便的实现链式操作。

而在 Python 中,语言语法本身提供的功能已经足够丰富,没有 callback hell 这类 问题,Future 则可以更专注的让我们可以将各种异步操作以一种顺序的、更接近人类 逻辑思维与自然语言方式描述出来:

import asyncio@asyncio.coroutinedef slow_operation(future):yield from asyncio.sleep(1)future.set_result('Future is done!')loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(slow_operation(future))
loop.run_until_complete(future)
print(future.result())
loop.close()

小结

在现在流行的编程语言中,异步、协程、事件循环被越来越多的关注与使用,Python 中的 asyncio.coroutine、Ruby 中的 Fiber、Node 中的 Promise、甚至像 Scala 这样的语言 中也有了 Future,更不用说 Golang 这种在这条路上一头走到底的编程语言。 await/Future 这样的异步编程方式被越来越多的普及与使用, 以后可能会像 if、for 一样成为编程语言不可缺少的一部分,而协程这种轻量级线程在 某些情境下可能也会越来越多的代替目前的多线程/多进程成为主流的并发编程方式。

美中不足的是,秉承 Guido 一向以来只挖坑不填坑的习惯,Python 3.5 实现新的 async/await 关键字后,并没有给出具体的现有第三方类库如何向新的 native 协程 实现迁移的方案,更不用说一些流行的 Python 2 第三方类库目前连 Python 3 都不 支持。距离我们真正能方便流畅地使用体验它可能还需要一段时间。

References

  • [1]: https://docs.python.org/3/library/asyncio.html

  • [2]: https://wiki.python.org/moin/GlobalInterpreterLock

  • [3]: https://www.python.org/dev/peps/pep-0492

  • [4]: https://www.python.org/dev/peps/pep-0380

  • [5]: https://www.python.org/dev/peps/pep-0342

  • [6]: https://github.com/promises-aplus/promises-spec

  • [7]: https://lwn.net/Articles/643786

免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击。

相关文章:
【推荐】 网易考拉规则引擎平台架构设计与实践
【推荐】 真屏实据丨数据大屏设计实战—揭秘企业级数据大屏设计过程
【推荐】 网易七鱼 Android 高性能日志写入方案

小议Python3的原生协程机制相关推荐

  1. Generator(生成器),入门初基,Coroutine(原生协程),登峰造极,Python3.10并发异步编程async底层实现

    普遍意义上讲,生成器是一种特殊的迭代器,它可以在执行过程中暂停并在恢复执行时保留它的状态.而协程,则可以让一个函数在执行过程中暂停并在恢复执行时保留它的状态,在Python3.10中,原生协程的实现手 ...

  2. Swoft 2 Beta 发布,基于 Swoole 的云原生协程框架

    Swoft 是首个基于 Swoole 原生协程的框架,从开发到发布据今已有2年多. 1.x 发布以来,已有大量的开发人员和企业使用,得到了大家的认可.从去年11月份开始,将近半年的时间从零开始,底层吸 ...

  3. php直出,Swoole2.0原生协程高性能开发实践--朱新宇@PHPCON2017

    1.Swoole2.0原生协程高性能开发实践 alvinzhu(朱新宇) 2.关于我 • 2015年 毕业于上海交通大学 • 2015年 进入腾讯即通综合部 • 企业QQ • 营销QQ • QQ看点 ...

  4. python3.4通过协程使用websockets

    python3.4通过协程使用websockets 1.event loop 1.这里的事件循环类似javascript中的时间循环,在线程中执行协程需要有调度程序,协程是用户级的线程,不涉及内核级别 ...

  5. python 协程 asyncio_Python 原生协程------asyncio(选自公众号)

    所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知. Asyncio 是并发(concurrency)的一种方式.对 Python 来说,并 ...

  6. Go语言入门到实战——14.Go语言的协程机制以及并发机制

    Go语言入门到实战--00主目录 在上一讲中我们学习了Go语言的包的知识已经依赖管理. 协程(groutine)是一种更加轻量级的线程(thread). 一.协程与线程得到比较 1.对于java而言, ...

  7. 探秘Kotlin协程机制

    什么是协程 场景1:异步回调嵌套 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fj2umvsw-1641110474367)(image-20211209143744 ...

  8. Swoft 1.0 正式来袭,首个基于 Swoole 原生协程的框架

    历时 1 年多紧锣密鼓的开发,以及愉快而忙碌的春节假期,期间 github star 数从 500 到快破 1k,码云首页推荐,Swoole 作者 Rango 和社区的大力支持,Swoft 1.0 正 ...

  9. Go 分布式学习利器(17)-- Go并发编程之协程机制:Grountine 原理及使用

    文章目录 1. Thread VS Groutine 2. Groutine 调度原理 3. Groutine 示例代码 关于Go的底层实现还需要后续持续研究,文中如有一些原理描述有误,欢迎指证. 1 ...

最新文章

  1. 聊聊jesque的几个dao
  2. 太励志!80 后草根创业者自学编程年收入上亿
  3. Android之靠谱的把图片和视频插入手机系统相册
  4. windows7安装和问题排除,绝对有用!
  5. 解决opensips NAT 问题和ACK不能正常转发问题
  6. Redis 3.2.4编译安装
  7. 批量修改联系人头像_视频号pr批量剪辑0基础实操课,pr批量处理伪原创一分钟一个视频【共2节】...
  8. apktool助手转java_apktool助手
  9. 计算机程序班搞笑口号,16字高一班级搞笑口号.docx
  10. [转]移动IIS7.5默认inetpub目录_se7en3_新浪博客
  11. JETT(五)-支持Excel公式
  12. 浅谈/proc/net/dev的由来
  13. 为什么同时需要IP地址和MAC地址
  14. 手把手教你静态代码检查工具TscanCode安装与使用
  15. qbo web接口分析
  16. 荣耀v30鸿蒙系统怎么升级,荣耀v30pro怎么升级鸿蒙系统
  17. docker 搭建响应式个人博客
  18. ZJOI2016——一个蒟蒻的爆〇经历
  19. NoneType‘ object has no attribute ‘loader‘
  20. php存在的两个问题,是导致不再受欢迎的主要原因

热门文章

  1. 基于SSH框架实际开发时遇到的问题及解决办法
  2. Phpstorm 9 关闭拼写检查
  3. ant自动打包多个android项目为apk
  4. Windows DPM 备份数据
  5. memcpy()函数
  6. ASP.NET ListView控件基本操作
  7. 官网的Ext direct包中.NET版的问题
  8. 区块链技术没那么复杂,别被大佬们忽悠晕了
  9. leetcode算法题解(Java版)-11-贪心大法
  10. Docker 镜像小结---操作指令介绍(七)