为啥我的进程列表在运行 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完了以后 就没反映了进程显示在运行 任务显示无响应