Python中国社区  »  Python

Python之tworoutine

Python程序员 • 2 月前 • 68 次点击  

本文主要描述了Python中的一种编码风格,它允许轻松编写混合同步和异步的代码。作为大型微波望远镜(包括南极望远镜)控制软件的一部分,我们已经成功地在Tornado/Python 2.x技术栈下使用这种代码。

不幸的是,Python 3.7中的体系结构变化与@tworoutine密不可分。为了促进对Python异步生态系统的热烈讨论,我描述了它们对我们如此有用的原因。

介绍

Python中的异步编程是由Twisted,Tornado和gevent等第三方库开创的。官方event loop(事件循环)实现落在Python3.4中,并在Python3.7中得到了显著扩展。像curio和trio这样的新型异步库正在继续提升异步io的性能。

还有一些关于Python异步生态系统的优秀文章。我不打算重述它们。但是,为了讲得清楚,我将提供一些链接,为后面的内容奠定基础。

  • PEP 3156--对异步io的支持重新启动:"asyncio"模块(https://www.python.org/dev/peps/pep-3156/)

  • 我不理解Python中的异步io(http://lucumr.pocoo.org/2016/10/30/i-dont-understand-asyncio/)

  • aysnc/await在Python中如何工作(https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/)

  • 控制Python中的异步蠕变(https://hackernoon.com/controlling-python-async-creep-ec0a0f4b79ba)

其中,最后一个可能是最有趣的,因为它试图解决我们在设计望远镜调整软件时遇到的同样问题:异步和同步编程在Python中是不同的,但它对于自由混合使用它们却是极其有用的。

为了混编同步和异步代码,这里简要描述了我们为调整望远镜编写的代码类型。

为望远镜编码

我的日常工作包括CMB望远镜的工作,包括南极洲的南极望远镜和智利阿塔卡马高原的西蒙斯阵列。

这些望远镜中的读出电子设备是一系列软件定义的无线电,其中有数千个发射器和接收器用于偏置和测量大爆炸的剩余标记。这些无线电在数百个定制板中实现,定制板载有安装在望远镜附近的板条箱中的FPGA,并由PC控制。这台PC启动并运行系统,控制低温冰箱,瞄准望远镜,并捕获它产生的大量数据。

整个调优,控制和分析堆栈大量地使用Python,以及C,C++和VHDL。(我对我们依赖的许多开源社区感到无比感激,当我能够以某种身份回馈时,这是一个很大的特权。)

可以想象,我们不只是将代码直接部署到望远镜上。随着望远镜本身的小规模安装,从电路板或台面上的两个,到世界各地大学实验室的低温设备箱。在开发过程中,代码可能在Jupyter笔记本或IPython shell中运行,可能只有一小部分电子设备或根本没有。在这里,交互式REPL会话用于原型算法,探索数据,并尝试新的调整和分析技术。

但是,对于在部署中有用的算法,它需要大规模运行。这里我们大量使用异步代码:与数百个电路板的命令交互非常适合异步编码风格。这导致产生以下工作流程:

设计流程,具有单独的异步/同步实现:

  • 原型代码,可能是同步的,主要专注于校对算法或技术;

  • 可以在交互式(ipython)环境中测试小规模部署的功能;

  • 使用异步方式重新编程实现算法;

  • 集成测试,优化和部署。

这种方法的优点:

  1. 在开发概念验证时,开发人员能够忽略性能并专注于他们试图解决的问题(物理,仪器,低温,电子)。

  2. 在原型设计过程中,交互式探索是非常有用的,同步代码可以促进对IPython或Jupyter等环境的使用。

但是,此工作流程有三个主要缺点:

  1. 它很笨拙:它需要编写和测试同步版本,然后批量转移到异步环境。这个工作流程很可能会循环往复很多次,因为在此过程中会引入许多错误。

  2. 尽管没有扩展到望远镜级别的性能,同步版本永远不会停止使用。在调试或试验时,我们通常更愿意拥有更简单的语义,更可预测的控制流和更短的与同步调用相关的错误跟踪。此外,它可以在REPL环境中方便地调用——如果望远镜正在运行并且我们需要进行一些快速手动调整,这是非常宝贵的。

  3. 它不可组合。多年来,我们已经建立了有用的调优和控制算法库,只要同步和异步代码保持不同,我们就无法在没有两个实现的情况下从较小的部分组成算法。

要求开发人员在不同的编码习惯下维护两个版本(并希望保持版本同步)是通过要求熟练的劳动者做琐碎的工作来解决技术缺陷;这通常是一个代价高昂的错误。(由于autoawait功能,异步代码的交互使用在IPython 7.0中变得更加容易。这个扩展解决了第二点而非第三点。)

相反,我们正在寻找一种自由混合异步和同步编码样式的方法。

@tworoutine

什么是@tworoutine?它是一个围绕异步函数的同步装饰器。

你需要源代码。你还需要nest_asyncio(https://github.com/erdewit/nest_asyncio)。

我们如何同步调用此函数?直接调用它吧!

这是怎么回事? @tworoutine装饰器返回一个类,其__call__方法是一个同步包装器,它获取一个事件循环并调用异步代码,阻塞直到它完成。因为我们希望同步调用方便且友好。

如果已经有一个事件循环在运行,那么这段代码相当有效(当然,除了是一个阻塞调用!)任何已在事件循环中排队的异步事件都可以继续执行。在协程完成之前,仅阻止当前执行上下文。

同步调用非常多。我们如何异步调用此函数?我们首先必须撤消或反转包装器并获取一个返回协程的引用。

除了函数名称周围的反转运算符外,这是普通的异步代码;除了操作符本身之外没有额外的开销。这是一个完整的示例,显示事件循环中的混合编码样式:

这里显而易见的好处是,当我们懒得携带事件循环或处理Python的异步编码习惯时,也可以使用同步的方式调用异步代码。

使用@tworoutine设计流程。同步和异步实现被替换为可以混合语法的单个实现。

感慨

使用@tworoutine的日子可能已经屈指可数了。 Python开发人员已经坚决地拒绝了这种编码风格:

issue 22239:asyncio:嵌套事件循环(https://bugs.python.org/issue22239)

我们几年来一直在南极和其他地方使用这种方法(在Python 2.7和Tornado

要完成同步@tworoutine调用,我们需要获取一个事件循环,调度异步调用,并阻塞直到它完成。目前在没有修补它的情况下,没有办法在Python 3.7 asyncio中这样做。调用堆栈中任何一点的异步代码必须只能通过异步调用链接到事件循环,一直向上。

要在此处显示的Python 3.7代码中解决此问题,我使用了nest_asyncio补丁。它是一个简短而有效的代码片段,但它与Python正统相反,并且在生产环境中采用这种补丁会带来极大的风险。

如果没有这个补丁,我们可以在Python 3.x上升级到Tornado 4.5,但是Tornado 5.0会转移到asyncio事件循环,这导致我们最终无法升级。

最终放弃

这里的代码示例已经从Python 2.7和Tornado 4.5转移到Python 3.7和纯asyncio。这是一个实验——这不是生产代码!


英文原文:http://threespeedlogic.com/python-tworoutines.html
译者:xiaocai



今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/fGn8IeSTqa
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/27714
 
68 次点击  
分享到微博