Почему мои асинхронные HTTP-запросы не перекрываются, как ожидалось, при вводе-выводе?Python

Программы на Python
Anonymous
 Почему мои асинхронные HTTP-запросы не перекрываются, как ожидалось, при вводе-выводе?

Сообщение Anonymous »

Я пытаюсь отправить несколько HTTP-запросов GET к разным конечным точкам одного и того же API. Насколько я понимаю, asyncio помогает мне эффективно управлять параллельными операциями ввода-вывода, то есть, когда один сетевой запрос ожидает данных, программа Python может переключиться на другую задачу вместо блокировки. Цель состоит в том, чтобы максимизировать перекрытие времени ожидания ввода-вывода, чтобы общее время выполнения было ближе к самому медленному отдельному запросу, а не к сумме всех из них.
Однако создается впечатление, что мои запросы не эффективно перекрывают время ожидания ввода-вывода. Когда я проверяю общее время, оно представляет собой примерно сумму времени отдельных запросов, а не время самого медленного запроса. Я знаю, что asyncio не обеспечивает истинного параллелизма, как это делают потоки или отдельные процессы (на стороне Python он по-прежнему однопоточный, поэтому для работы, связанной с процессором, нельзя обойти GIL), но для задач, связанных с сетью, я ожидаю значительного параллелизма.
Я использую Python 3.12.0.
/>Вот код, который я использую. Я заменил свой внутренний API общедоступными заполнителями, но в моих собственных службах такое же поведение сохраняется.

Код: Выделить всё

import asyncio
import aiohttp
import time

async def fetch_data(session, url):
"""
Fetches data from a given URL using aiohttp.
Includes print statements to track apparent start/finish times.
"""
start_req_time = time.perf_counter()
print(f"[{time.time():.2f}] Starting request for: {url}")
try:
async with session.get(url) as response:
response.raise_for_status() # Raise an exception for bad status codes
data = await response.json() # Or .text(), depending on expected response
end_req_time = time.perf_counter()
print(f"[{time.time():.2f}] Finished request for: {url} in {end_req_time - start_req_time:.4f}s")
return {"url": url, "status": response.status, "data_length": len(str(data))}
except aiohttp.ClientError as e:
print(f"[{time.time():.2f}] Error fetching {url}: {e}")
return {"url": url, "error": str(e)}

async def main():
# Using a list of public JSON placeholder URLs for demonstration
# In my real app, these are different endpoints of my own API
urls = [
"https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/todos/2",
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/users/1",
"https://jsonplaceholder.typicode.com/comments/1",
]

total_start_time = time.perf_counter()

async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
tasks.append(fetch_data(session, url))

# This is where I expect the I/O waits of tasks to overlap
results = await asyncio.gather(*tasks)

total_end_time = time.perf_counter()

print(f"\n[{time.time():.2f}] All tasks completed in {total_end_time - total_start_time:.4f} seconds.")
print("\n--- Results ---")
for result in results:
print(result)

if __name__ == "__main__":
asyncio.run(main())

Когда я запускаю это, я получаю такой вывод (временные метки могут различаться, но проблема в шаблоне):

Код: Выделить всё

[1678886400.10] Starting request for: https://jsonplaceholder.typicode.com/todos/1
[1678886400.25] Finished request for: https://jsonplaceholder.typicode.com/todos/1 in 0.1500s
[1678886400.25] Starting request for: https://jsonplaceholder.typicode.com/todos/2
[1678886400.40] Finished request for: https://jsonplaceholder.typicode.com/todos/2 in 0.1500s
[1678886400.40] Starting request for: https://jsonplaceholder.typicode.com/posts/1
[1678886400.55] Finished request for: https://jsonplaceholder.typicode.com/posts/1 in 0.1500s
...  and so on ...

[1678886400.80] All tasks completed in 0.7000 seconds.
Проблема в том, что операторы печати «Начальный запрос» и «Завершенный запрос» часто появляются последовательно или с очень небольшим перекрытием, что увеличивает общее время выполнения (

Код: Выделить всё

0.7000 seconds
), близкое к сумме времени отдельных запросов (

Код: Выделить всё

5 * ~0.15s = ~0.75s
), а не что-то гораздо более быстрое (например, ~0,15–0,2 с), как я ожидал бы от одновременно управляемого сетевого ввода-вывода.
Что мне здесь не хватает? Есть ли какая-то общая ошибка с ClientSession aiohttp или asyncio.gather, которая приводит к такой сериализации для сетевых задач, даже с await? Может ли это быть связано с разрешением DNS, ограничениями TCP-соединений (локальными или на сервере) или чем-то в настройке ClientSession?
Я проверил:
  • Версия Python (3.12.0)
  • версия (

    Код: Выделить всё

    pip list
    показывает aiohttp==3.9.3)
  • В моей функции fetch_data нет очевидного ожидания.


Подробнее здесь: https://stackoverflow.com/questions/798 ... ed-for-i-o

Вернуться в «Python»