Исполняется ли асинхронный код Python в одном потоке?

Ответ

Асинхронный код в 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 переключиться на другую задачу, пока первая ожидает, создавая видимость параллельного выполнения.