Ответ
Асинхронный код в Python (с использованием asyncio) исполняется в одном потоке. Он достигает конкурентности, а не параллелизма, благодаря механизму event loop.
Как это работает:
Event loop переключается между корутинами (async/await), когда они сталкиваются с неблокирующими операциями ввода-вывода (I/O), такими как сетевые запросы или чтение файлов. Это позволяет одному потоку эффективно управлять множеством задач, не дожидаясь завершения каждой из них.
Почему это важно:
- Эффективность I/O: Идеально подходит для I/O-bound задач, где основное время тратится на ожидание внешних ресурсов.
- GIL: Global Interpreter Lock (GIL) в Python ограничивает выполнение байткода Python одним потоком одновременно. Асинхронность обходит это ограничение для I/O, так как поток освобождается во время ожидания.
- Отличие от многопоточности: Асинхронность не использует несколько потоков для выполнения кода Python одновременно, в отличие от многопоточности, которая может использовать несколько потоков, но все еще ограничена GIL для CPU-bound задач.
Пример:
import asyncio
import time
async def fetch_data(task_id: int):
"""Имитирует асинхронную I/O операцию."""
print(f"Задача {task_id}: Начало получения данных...")
await asyncio.sleep(1) # Неблокирующее ожидание
print(f"Задача {task_id}: Данные получены.")
return f"Данные для задачи {task_id}"
async def main():
start_time = time.monotonic()
# Запускаем две задачи "одновременно" в одном потоке
results = await asyncio.gather(
fetch_data(1),
fetch_data(2)
)
end_time = time.monotonic()
print(f"Все задачи завершены за {end_time - start_time:.2f} секунд.")
print(f"Результаты: {results}")
if __name__ == "__main__":
asyncio.run(main())
В этом примере обе задачи fetch_data выполняются в одном потоке, но asyncio.sleep(1) позволяет event loop переключиться на другую задачу, пока первая ожидает, создавая видимость параллельного выполнения.