python 异步处理

异步编程(简称异步)是许多现代语言的功能,它使程序可以处理多个操作,而无需等待或挂断其中的任何一个。 这是一种有效处理网络或文件I / O等任务的明智方法,其中程序的大部分时间都花在等待任务完成上。

考虑一个打开100个网络连接的Web抓取应用程序。 您可以打开一个连接,等待结果,然后打开下一个连接并等待结果,依此类推。 程序运行的大部分时间都花在等待网络响应上,而不是在做实际的工作。

[ 同样在InfoWorld上:每个Python开发人员都有24个Python库 ]

异步为您提供了一种更有效的方法:一次打开所有100个连接,然后在返回结果时在每个活动连接之间切换。 如果一个连接没有返回结果,请切换到下一个,依此类推,直到所有连接都返回了它们的数据。

异步语法现在已成为Python的标准功能,但是习惯于一次做一件事的长期Pythonista使用者可能难以解决。 在本文中,我们将探讨异步编程如何在Python中工作以及如何使用它。

请注意,如果要在Python中使用异步,最好使用Python 3.7或Python 3.8(撰写本文时的最新版本)。 我们将使用该语言的这些版本中定义的Python异步语法和辅助函数。

何时使用异步编程

通常,使用异步的最佳时间是当您尝试执行具有以下特征的工作时:

  • 这项工作需要很长时间才能完成。
  • 延迟涉及等待I / O(磁盘或网络)操作,而不是计算。
  • 这项工作涉及一次执行许多I / O操作, 或者当您还试图完成其他任务时进行一项或多项I / O操作。

异步允许您并行设置多个任务并有效地遍历它们,而不会阻塞应用程序的其余部分。

可以很好地与异步工作的一些任务示例:

  • 网页抓取,如上所述。
  • 网络服务(例如,Web服务器或框架)。
  • 协调来自多个源的结果的程序,这些结果需要很长时间才能返回值(例如,同时进行的数据库查询)。

重要的是要注意,异步编程不同于多线程或多处理。 异步操作都在同一个线程中运行,但是它们根据需要相互转化,这使得异步处理比线程或多处理多种任务的效率更高。 (更多有关此内容。)

[ 同样在InfoWorld上:Python 3.8中最好的新功能 ]

Python async awaitasyncio

Python最近添加了两个关键字asyncawait ,用于创建异步操作。 考虑以下脚本:

def get_server_status(server_addr)
    # A potentially long-running operation ...
    return server_status

def server_ops()
    results = []
    results.append(get_server_status('addr1.server')
    results.append(get_server_status('addr2.server')
    return results

相同脚本的异步版本(不起作用,仅足以让我们了解语法的工作原理)可能看起来像这样。

async def get_server_status(server_addr)
    # A potentially long-running operation ...
    return server_status

async def server_ops()
    results = []
    results.append(await get_server_status('addr1.server')
    results.append(await get_server_status('addr2.server')
    return results

async关键字为前缀的函数成为异步函数,也称为coroutines 。 协程的行为与常规函数不同:

  • 协程可以使用另一个关键字await ,它允许协程等待来自另一个协程的结果而不会阻塞。 在await协程返回结果之前,Python在其他正在运行的协程中自由切换。
  • 协程只能从其他async函数中调用。 如果您从脚本主体中按原样运行server_ops()get_server_status() ,则不会得到它们的结果。 您将获得一个Python协程对象,该对象不能直接使用。

所以,如果我们不能称之为async非异步函数的功能,我们不能运行的async功能直接,我们如何使用它们? 答:通过使用asyncio库,它将async与Python的其余部分联系起来。

[ 同样在InfoWorld上:每种编程需要12个Python ]

Python async awaitasyncio示例

这是一个示例(再次,不是功能性的,而是说明性的)如何使用asyncasyncio编写Web抓取应用程序。 该脚本获取URL列表,并使用来自外部库( read_from_site_async() )的async函数的多个实例来下载它们并汇总结果。

import asyncio
from web_scraping_library import read_from_site_async

async def main(url_list):
    return await asyncio.gather(*[read_from_site_async(_) for _ in url_list])

urls = ['http://site1','http://othersite','http://newsite']
results = asyncio.run(main(urls))
print (results)

在上面的示例中,我们使用两个常见的asyncio函数:

  • asyncio.run()用于从代码的非异步部分启动async功能,从而启动所有progam的异步活动。 (这就是我们运行main() 。)
  • asyncio.gather()接受一个或多个异步装饰的函数(在这种情况下,是我们假设的网络抓取库中的read_from_site_async()几个实例),全部运行它们,然后等待所有结果输入。

这里的想法是,我们立即开始所有站点的读取操作,然后在它们到达时收集结果(因此asyncio.gather() )。 在进行下一个操作之前,我们不等待任何一项操作完成。

[ 同样在InfoWorld上:Python virtualenv和venv做和不做 ]

Python异步应用程序的组件

我们已经提到了Python异步应用程序如何使用协程作为主要成分,并利用asyncio库运行它们。 其他一些要素也是Python异步应用程序的关键:

事件循环

asyncio库创建和管理事件循环 ,即运行协程直到完成的机制。 在Python进程中,一次只能运行一个事件循环,如果这样做只是为了使程序员更容易跟踪其中的内容。

任务

将协程提交到事件循环以进行处理时,可以获取Task对象,该对象提供了一种从事件循环外部控制协程行为的方法。 例如,如果需要取消正在运行的任务,可以通过调用任务的.cancel()方法来完成。

这是站点抓取脚本的略有不同的版本,该脚本显示了事件循环和正在执行的任务:

import asyncio
from web_scraping_library import read_from_site_async

tasks = []

async def main(url_list):    
    for n in url_list:
        tasks.append(asyncio.create_task(read_from_site_async(n)))
    print (tasks)
    return await asyncio.gather(*tasks)

urls = ['http://site1','http://othersite','http://newsite']
loop = asyncio.get_event_loop()
results = loop.run_until_complete(main(urls))
print (results)

该脚本更明确地使用事件循环和任务对象。

  • .get_event_loop()方法为我们提供了一个对象,该对象使我们可以通过.run_until_complete()以编程方式向事件循环提交异步函数,从而直接控制事件循环。 在先前的脚本中,我们只能使用asyncio.run()运行单个顶级异步函数。 顺便说一句, .run_until_complete()完全按照其说的去做:它运行所有提供的任务直到完成,然后分批返回其结果。
  • .create_task()方法使用一个要运行的函数(包括其参数),并给我们提供了一个Task对象来运行它。 在这里,我们将每个URL作为单独的Task提交到事件循环,并将Task对象存储在列表中。 请注意,我们只能在事件循环内(即,在async函数内)执行此操作。

您需要对事件循环及其事件进行多少控制,将取决于您所构建的应用程序的复杂程度。 如果您只想提交一组固定的作业以同时运行,就像使用我们的Web抓取工具一样,您将不需要太多控制权-仅足以启动作业和收集结果。

相比之下,如果您要创建一个完善的Web框架,则需要对协程和事件循环的行为进行更多控制。 例如,在应用程序崩溃的情况下,您可能需要优雅地关闭事件循环 ,或者如果从另一个线程调用事件循环, 则以线程安全的方式运行任务 。

[ 也在InfoWorld上:Anaconda入门,Anaconda是数据科学的Python发行版 ]

异步与线程与多处理

在这一点上,您可能想知道,为什么使用异步而不是线程或多处理,而这在Python中早已可用?

首先,异步和线程或多处理之间有一个关键区别,即使这些内容是如何在Python中实现的也是如此。 异步与并发有关,而线程和多处理则与并行有关。 并发涉及一次在多个任务之间高效地分配时间,例如,在杂货店等待注册时检查电子邮件。 并行涉及多个代理并排处理多个任务,例如,在杂货店打开五个单独的寄存器。

大多数情况下,异步是线程的良好替代品, 因为线程是在Python中实现的 。 这是因为Python不使用OS线程,而是使用自己的协作线程,在解释器中一次仅运行一个线程。 与协作线程相比,异步提供了一些关键优势:

  • 异步函数比线程轻得多。 一次运行的成千上万的异步操作将比成千上万的线程少得多的开销。
  • 异步代码的结构使人们更容易推断任务从何处提取。 这意味着数据争用和线程安全性不再是问题。 由于异步事件循环中的所有任务都在单个线程中运行,因此Python(和开发人员)可以更轻松地序列化它们访问内存中对象的方式。
  • 与线程相比,可以更轻松地取消和操作异步操作。 我们从asyncio.create_task()返回的Task对象为我们提供了一种方便的方法。

另一方面,Python中的多处理最适合CPU密集而不是I / O密集的作业。 异步实际上与多处理并驾齐驱,因为您可以使用asyncio.run_in_executor()从中央进程将CPU密集型作业委派给进程池,而不会阻塞该中央进程。

[ 通过InfoWorld的App Dev Report新闻通讯了解软件开发中的热门话题 ]

Python异步的后续步骤

最好的第一件事是构建自己的一些简单异步应用程序。 现在有很多很好的例子 ,Python中的异步编程已经经历了几个版本,并且花了几年时间才逐渐发展起来,并得到更广泛的使用。 即使您不打算使用其所有功能, asyncio的官方文档也值得阅读以了解其功能。

您可能还会探索越来越多的异步驱动的库和中间件,其中许多提供了数据库连接器,网络协议等的异步,非阻塞版本。 aio-libs存储库具有一些关键的存储库 ,例如用于Web访问的aiohittp库。 也值得在Python软件包索引中搜索带有async关键字的库。 对于异步编程之类的东西,最好的学习方法是看别人如何使用它。

翻译自: https://www.infoworld/article/3454442/get-started-with-async-in-python.html

python 异步处理

更多推荐

python 异步处理_开始使用Python进行异步处理