小议Python3的原生协程机制

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

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

在最近发布的 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 asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)    return x + y

async 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 [email protected] 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

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

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

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

原文地址:https://www.cnblogs.com/163yun/p/9895601.html

时间: 2024-11-12 12:03:44

小议Python3的原生协程机制的相关文章

协程,greenlet原生协程库, gevent库

yield表达式 在了解协程之前,需要先了解一下生成器中的yield,它不仅可以当做生成器,还能当做一个表达式来使用(yield) def func(): x = (yield) print(x) x = (yield) g = func() print(next(g)) # 这是第一个yield,就暂停了 g.send('hello world') # 恢复暂停位置,将第一个yield赋值, # x = hello world,然后又执行到yield,暂停 --> None hello wor

Python与协程从Python2—Python3

协程,又称微线程.纤程,英文名Coroutine:用一句话说明什么是线程的话:协程是一种用户态的轻量级线程. Python对于协程的支持在python2中还比较简单,但是也有可以使用的第三方库,在python3中开始全面支持,也成为python3的一个核心功能,很值得学习. 协程介绍 协程,又称微线程.纤程,英文名Coroutine:用一句话说明什么是线程的话:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先

Python3 协程相关

什么是协程 协程的优势 Python3中的协程 生成器 yield/send yield + send(利用生成器实现协程) 协程的四个状态 协程终止 @asyncio.coroutine和yield from asyncio.coroutione yield from 为什么要用yield from async/await关键字 什么是协程 ??协程(Coroutine),又称微线程,纤程.通常我们认为线程是轻量级的进程,因此我们也把协程理解为轻量级的线程即微线程. ??协程的作用是在执行函数

(zt)Lua的多任务机制——协程(coroutine)

原帖:http://blog.csdn.net/soloist/article/details/329381 并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制.大致上有这么两种多任务技术,一种是抢占式多任务(preemptive multitasking),它让操作系统来决定何时执行哪个任务.另外一种就是协作式多任务(cooperative multitasking),它把决定权交给任务,让它们在自己认为合适的时候自愿放弃执行.这两种多任务方式各有优缺点,前者固

用python3的多进程和协程处理MySQL的数据

本文介绍用python3的多进程 + 协程处理MySQL的数据,主要逻辑是拉取MySQL的数据,然后使用flashtext匹配关键字,在存回MySQL,代码如下(async_mysql.py): import time import asyncio import random from concurrent.futures import ProcessPoolExecutor as Pool import aiomysql from flashtext import KeywordProcess

python线程、进程和协程

链接:http://www.jb51.net/article/88825.htm 引言 解释器环境:python3.5.1 我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程.多进程的模块.一般我们在socketserver服务端代码中都会写这么一句: server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer) Threadi

python之线程、进程和协程

引言 解释器环境:python3.5.1我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程.多进程的模块.一般我们在socketserver服务端代码中都会写这么一句:server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer)ThreadingTCPServer这个类是一个支持多线程和TCP协议的socketserver,它的

python 高性能编程之协程

用 greenlet 协程处理异步事件 自从 PyCon 2011 协程成为热点话题以来,我一直对此有着浓厚的兴趣.为了异步,我们曾使用多线程编程.然而线程在有着 GIL 的 Python 中带来的性能瓶颈和多线程编程的高出错风险,"协程 + 多进程"的组合渐渐被认为是未来发展的方向.技术容易更新,思维转变却需要一个过渡.我之前在异步事件处理方面已经习惯了回调 + 多线程的思维方式,转换到协程还非常的不适应.这几天我非常艰难地查阅了一些资料并思考,得出了一个可能并不可靠的总结.尽管这个

关于Python的协程问题总结

协程其实就是可以由程序自主控制的线程 在python里主要由yield 和yield from 控制,可以通过生成者消费者例子来理解协程 利用yield from 向生成器(协程)传送数据# 传统的生产者-消费者是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁.# 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,换回生产者继续生产,效率极高 def consumer(): r = '' while True: n = y