了解是什么阻塞了事件循环

7

我有成千上万个异步任务正在运行。

某些需要消耗大量 CPU 时间的任务需要大约 10 秒才能完成。

这导致程序无法正常工作,因为有些任务需要在他们的网络连接中回复消息,比如说在 5 秒内。

我的当前想法是以某种方式拦截事件循环。 在 asyncio 模块中,必须有一些区域在每次 epoll() 或 select() 之间执行所有当前活动任务的事件循环中。如果我能在每个任务“恢复”之前插入“elapsed = time.time()”,然后在每个任务“恢复”之后插入“elapsed = time.time() - elapsed ”,我认为就足以找出花费太多时间的任务了。

我认为相关的代码可能在此处,位于第 79 行: https://github.com/python/cpython/blob/master/Lib/asyncio/events.py

def _run(self):
    try:
        self._context.run(self._callback, *self._args)
    except (SystemExit, KeyboardInterrupt):
        raise
    except BaseException as exc:
        cb = format_helpers._format_callback_source(
            self._callback, self._args)
        msg = f'Exception in callback {cb}'
        context = {
            'message': msg,
            'exception': exc,
            'handle': self,
        }
        if self._source_traceback:
            context['source_traceback'] = self._source_traceback
        self._loop.call_exception_handler(context)
    self = None  # Needed to break cycles when an exception occurs.

但我不知道在这里该怎么做才能打印出有用的信息。我需要一种方法来识别这个 "self._context.run (...)" 将执行哪一行我的代码。

我已经过去了最后5个不眠之夜试图修复我的代码,但没有成功。

我尝试使用CProfiler、line_profile,但它们都没有帮助。它们告诉我执行函数所需的时间以及每行所花费的时间。我需要找出的是在每次循环迭代之间代码所需的时间。

所有我尝试过的性能分析/调试工具都没有给我任何线索应该修复什么问题。即使我用不同的方式重写了相同的程序约15次,我仍然无法让它正常工作。

我只是一个非专业的程序员,还是 Python 的新手,但如果我不能解决这个问题,那么下一步就是学习Rust,这本身就会是一个巨大的痛苦,可能在我开始学习之后三年,我才能让它工作,而本应该只需要两个月的时间。


你能否只是做这个 - Mike Dunlavey
2个回答

4

顺便提一句,asyncio 内置了一个很酷的功能(你可以在这里看到代码源:这里),它会告诉你是否存在“阻塞”函数。

你只需要启用调试模式(对于负载测试非常有用)。

如何启用调试模式 - 你可以在这里找到所有选项。


我正在运行uvicorn,而我在日志中看到的只是类似于Executing <Task finished name='Task-13' coro=<RequestResponseCycle.run_asgi() done, defined at .../python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py:424> result=None created at .../python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py:303> took 0.294 seconds的内容。 这只是asyncio调试模式的限制,还是我在这里做错了什么? - undefined

3

您好,最近编辑了文件/usr/lib/python3.7/asyncio/events.py,并添加了如下代码:

import time
import signal
import traceback

START_TIME = 0

def handler(signum, frame):
    print('##########', time.time() - START_TIME)
    traceback.print_stack()

signal.signal(signal.SIGALRM, handler)

并且在第79行:

    def _run(self):
        global START_TIME
        try:
            signal.alarm(3)
            START_TIME = time.time()
            self._context.run(self._callback, *self._args)
            signal.alarm(0)
        except Exception as exc:
            cb = format_helpers._format_callback_source(
                self._callback, self._args)
            msg = f'Exception in callback {cb}'
            context = {
                'message': msg,
                'exception': exc,
                'handle': self,
            }
            if self._source_traceback:
                context['source_traceback'] = self._source_traceback
            self._loop.call_exception_handler(context)
        self = None  # Needed to break cycles when an exception occurs.

现在,每次异步代码块阻塞事件循环3秒钟,都会显示一条消息。

我发现我的问题出在一个简单的"BeautifulSoup(page, 'html.parser')"上,其中page是一个1MB大小带有大型表格的HTML文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接