首页 > 解决方案 > asyncio 在多个工作人员中从同步功能运行

问题描述

我真的很难理解异步事件循环和多个工人/线程/进程之间的交互。

我正在使用 dash: 它在内部使用烧瓶和 gunicorn。

说我有两个功能

def async_download_multiple_files(files):
    # This function uses async just so that it can concurrently send
    # Multiple requests to different webservers and returns data.
def sync_callback_dash(files):
    # This is a sync function that is called from a dash callback to get data
    asyncio.run(async_download_multiple_files(files))

据我了解,asyncio.run在事件循环中运行异步函数但会阻止它:来自Python Docs

当一个任务在事件循环中运行时,没有其他任务可以在同一个线程中运行。

但是当我运行像 Gunicorn 这样的 WSGI 服务器时会发生什么?

假设有 2 个请求同时进入,可能会sync_callback_dash因为多个 Gunicorn 工作人员而并行发生多个调用。

请求 1 和请求 2 都可以尝试asyncio.run在不同的线程\进程中并行执行吗?一个会阻止另一个吗?

如果它们可以并行运行,那么Gunicorn提供的异步工作器有什么用?

标签: pythonmultithreadingasynchronous

解决方案


我回答这个问题的假设是对线程/进程/异步循环的一些基本理解缺乏一些知识。如果没有,请原谅我的详细信息。

首先要注意的是进程和线程是两个独立的概念。这个答案可能会给你一些背景信息。扩大:

进程由 CPU 直接运行,如果 CPU 有多个内核,则可以并行运行进程。内部进程是运行线程的地方。每个进程总是至少有 1 个线程,但可以有更多。如果还有更多,则进程在每(特定)毫秒后(由超出本问题范围的事情决定)在它正在执行的线程之间切换 - 因此线程不是绝对并行运行,而是不断地切换进出CPU(至少与 Python 相关,特别是由于称为 GIL 的东西)。异步循环在线程内运行,并切换与 I/O 绑定指令相关的上下文(更多内容见下文)。

关于这个问题,值得注意的是 Gunicorn 工作人员是进程,而不是线程(尽管您可以增加每个工作人员的线程数量)。

异步代码(使用async defawaitasyncio)的目的是提高性能,因为它特别涉及 I/O 绑定任务。诸如从磁盘获取文件、发送/接收网络请求或任何需要计算机物理部件(无论是 SSD 还是网卡)而不是 CPU 来完成某些工作的东西。它也可以用于大型 CPU 密集型指令,但这通常是线程进来的地方。请注意,I/O 密集型指令比 CPU 密集型指令慢得多,因为计算机内部的电流实际上也必须传输更远的距离在硬件级别执行额外的步骤(以保持简单)。

这些任务只是在等待回复上浪费了 CPU 时间(或者更具体地说,是当前进程的时间)。异步代码在loop自动管理 I/O 绑定指令和普通 CPU 绑定指令的上下文切换的帮助下运行(取决于使用await关键字)通过利用函数可以将控制“让给”回循环的想法,并允许循环在等待时继续处理其他代码段。当异步代码发送一个 I/O 绑定指令(例如从网卡中获取最新的数据包)时,它不会坐在那里等待回复,而是将当前进程的上下文切换到其列表中的下一个任务以加快一般执行时间(将先前的 I/O 绑定调用添加到此列表以稍后再检查)。还有更多内容,但这是与您的问题相关的一般要点。

这就是文档说的意思:

当一个任务在事件循环中运行时,没有其他任务可以在同一个线程中运行。

异步循环不是并行运行事物,而是不断在不同指令之间切换上下文,以实现更优化的 CPU + I/O 关系/执行。

另一方面,假设您有多个内核,进程在您的 CPU 中并行运行。Gunicorn 工作者 - 如前所述 - 是流程。当您使用 Gunicorn 运行多个异步工作者时,您实际上是asyncio.loop在多个(独立和并行运行)进程中运行多个。这应该回答您的问题:

请求 1 和请求 2 都可以尝试在不同的线程\进程中并行执行 asyncio.run 吗?一个会阻止另一个吗?

如果曾经有一个工作人员被卡在一些极长的 I/O 绑定(甚至是非异步计算)指令上,其他工作人员会在那里处理下一个请求。


推荐阅读