为啥我的进程列表在运行 aiohttp 时会显示多个线程?

Posted

技术标签:

【中文标题】为啥我的进程列表在运行 aiohttp 时会显示多个线程?【英文标题】:Why does my process list show multiple threads when running aiohttp?为什么我的进程列表在运行 aiohttp 时会显示多个线程? 【发布时间】:2022-01-20 11:46:13 【问题描述】:

我目前在我的一个使用 asyncio 的项目中使用 aiohttp。在搜索了内存使用量过高的原因后,我发现 aiohttp 似乎在后台创建线程。

我已将我的代码分解为显示我的问题的最小代码。

import asyncio
import aiohttp
from aiohttp import ClientSession

async def test1(link, session):
    async with session.get(
        link,
    ) as r:
        print(r.status)
        await asyncio.sleep(10)

async def test():
    async with ClientSession(
        cookie_jar=aiohttp.DummyCookieJar(),
) as session:
        await asyncio.gather(test1("https://google.com", session))

loop = asyncio.get_event_loop()
loop.run_until_complete(test())
loop.close()

当使用ps -e -T |grep python3 运行它时,我得到以下输出,这很奇怪,因为它看起来像是创建了一个线程:

 160304  160304 pts/5    00:00:00 python3
 160304  160306 pts/5    00:00:00 python3

如果我将 asyncio.gather 更改为使用另外一个 test1 函数并再次运行 ps 命令,我会得到三个线程:

 160414  160414 pts/5    00:00:00 python3
 160414  160416 pts/5    00:00:00 python3
 160414  160417 pts/5    00:00:00 python3

这看起来很有问题,因为我的假设是 aiohttp 在单个线程中使用事件循环,这就是为什么我使用ThreadPoolExecutor 在程序开始时启动指定数量的线程。如果 aiohttp 为每个 session.get 请求创建一个新线程,那么线程数可能是 X 指定线程 * 当前正在运行的 HTTP 请求。

有关我正在使用的更多上下文:

Python 3.8.10 Ubuntu 20.04.3 LTS

我的主程序的目的是尽快保存X个域的html。当前的架构是使用ThreadPoolExecutor 来启动 Y 数量的线程并在整个应用程序生命周期中使用它,然后每个线程同时使用 session.get 和 asyncio.gather 发送 Z 数量的 HTTP 请求。这是错误的方法吗?我应该使用另一个 Python 库而不是 aiohttp 吗?线程与事件循环的结合是多余的吗?

我在网上四处搜索,但没有找到这个问题的答案,所以我谦虚地向社区征求任何明智的意见。

【问题讨论】:

您是否期望一个线程同时执行对sleep 的两个不同调用? 我将端点更改为 localhost 并使用 sleep(10) 创建了一个名为 sleep.php 的文件;作为现在唯一的代码行,并且看到 asyncio 不会创建更多线程。我只是添加 asyncio.sleep 来演示这个问题,但在演示这个问题时,问题似乎出在我的编程逻辑中。我的假设是 asyncio 永远不会创建更多线程,但在最坏的情况下会阻塞。也许这就是我的实际程序出现问题的原因。您是否知道是否可以防止 asyncio 阻塞而不是创建更多线程? 我不太确定你在问什么。如果您打算同时调用两次sleep,则需要两个线程。一个线程必须休眠 - 如果没有另一个线程,您将如何继续处理在该线程休眠时完成的异步 I/O? 问题不在于睡眠部分,我只使用睡眠,所以我有足够的时间运行 ps -e -T |grep python3 并检查正在运行的线程数。问题是当我调用 session.get 时 asyncio 正在创建线程,我的假设是 asyncio 是一个单线程事件循环。 看起来我可以通过使用 asyncio 包中的 Semaphore 来缓解这个问题,不过感觉有点捷径。 【参考方案1】:

asyncio 在后台始终至少有一个线程池,其中启动了 min(32, (os.cpu_count() or 1) + 4) 线程。

asyncio 在内部使用该池进行 DNS 查找。

此外,即使您将aiohttp 设置为使用aiodns 进行DNS 解析,默认的异步池仍然存在(虽然什么都不做)。

反过来,aiohttp 使用默认线程池进行一些操作,主要用于本地文件处理。

例如,await session.post(url, data=open('filename', 'rb')) 读取文件块以便在线程中发送;它有助于避免长时间阻塞调用。

【讨论】:

以上是关于为啥我的进程列表在运行 aiohttp 时会显示多个线程?的主要内容,如果未能解决你的问题,请参考以下文章

Python有了asyncio和aiohttp在爬虫这类型IO任务中多线程/多进程还有存在的必要吗?

为啥我的EVE进入登录界面 启动的LOGO完了以后 就没反映了进程显示在运行 任务显示无响应

为啥我的 python 脚本没有显示为进程,即使它正在运行?

为啥我的输入通过管道发送到进程时会延迟?

为啥新的 ElasticSearch 安装运行这么多进程?

python爬虫之多线程threading多进程multiprocessing协程aiohttp 批量下载图片