← Назад к вопросам

За счёт чего реализуются асинхронные операции в Python?

2.0 Middle🔥 231 комментариев
#Python Core#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает асинхронность в Python

Асинхронные операции в Python — это не параллелизм (threading), а конкурентность (concurrency). Это очень важное различие.

Основной механизм: Event Loop

Асинхронность в Python реализуется через event loop (цикл событий):

import asyncio

async def fetch_data():
    print("Starting fetch...")
    await asyncio.sleep(2)  # Имитация сетевого запроса
    print("Data fetched!")
    return "data"

# Запуск
asyncio.run(fetch_data())

Что происходит за кулисами:

  1. Event Loop запускается
  2. Вызывает fetch_data(), который асинхронная функция (определена через async)
  3. Когда встречает await, он паузирует текущую задачу (не замораживает программу!)
  4. Переключается на другую задачу, если она есть
  5. Когда операция завершена, возобновляет задачу
  6. Повторяет, пока все задачи не завершены

Ключевое: это НЕ многопоточность

# ❌ НЕПРАВИЛЬНО: это не параллелизм
async def task1():
    await asyncio.sleep(1)
    return "Task 1"

async def task2():
    await asyncio.sleep(1)
    return "Task 2"

# Они работают ПОСЛЕДОВАТЕЛЬНО (2 секунды)
asyncio.run(task1())
asyncio.run(task2())

# ✅ ПРАВИЛЬНО: конкурентно (1 секунда)
asyncio.run(asyncio.gather(task1(), task2()))

Как это устроено на низком уровне

Event Loop работает на одном потоке, но переключает контекст между корутинами:

import asyncio
import time

async def task_a():
    print(f"[{time.time():.2f}] Task A started")
    await asyncio.sleep(1)
    print(f"[{time.time():.2f}] Task A done")

async def task_b():
    print(f"[{time.time():.2f}] Task B started")
    await asyncio.sleep(1)
    print(f"[{time.time():.2f}] Task B done")

async def main():
    # Запускаются ВСЕ задачи одновременно
    await asyncio.gather(task_a(), task_b())

# Вывод (примерно):
# [0.00] Task A started
# [0.00] Task B started
# [1.00] Task A done
# [1.00] Task B done
# Время выполнения: ~1 секунда, а не 2!

Асинхронные vs Синхронные операции

import asyncio
import aiohttp
import requests

# ❌ СИНХРОННО (блокирующее)
def sync_fetch():
    response = requests.get('https://api.example.com/data')  # Ждет!
    return response.json()

# ✅ АСИНХРОННО (неблокирующее)
async def async_fetch():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.example.com/data') as resp:
            return await resp.json()

# Синхронно: ждем 5 секунд за 5 запросов
for i in range(5):
    sync_fetch()  # 5 секунд

# Асинхронно: ждем 1 секунду за 5 запросов
async def main():
    tasks = [async_fetch() for _ in range(5)]
    await asyncio.gather(*tasks)  # 1 секунда

Компоненты асинхронности

1. Event Loop (Цикл событий)

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(some_coroutine())
loop.close()

# Или просто:
asyncio.run(some_coroutine())  # Создает, запускает и закрывает loop

2. Корутины (async def)

async def coroutine():
    await some_async_operation()

3. Задачи (Tasks)

# Корутина становится Task, когда завернута в gather или create_task
task = asyncio.create_task(coroutine())
await task

4. Будущие значения (Futures)

# Низкоуровневый объект, представляющий отложенный результат
future = asyncio.Future()
# Потом где-то:
future.set_result("result")
# И кто-то ждет:
await future

Разница между await и без await

async def slow_operation():
    await asyncio.sleep(2)
    return "done"

async def main():
    # ❌ БЕЗ await — запускает, но не ждет
    coro = slow_operation()  # Создает корутину, но не выполняет
    print("This prints immediately!")
    # Потом используем:
    result = await coro  # Теперь ждем
    print(result)
    
    # ✅ С await
    result = await slow_operation()  # Правильно
    print(result)

Контекст переключения

Event Loop переключается между задачами в точках await:

async def demo():
    print("1. Start")
    await asyncio.sleep(0)  # Точка переключения!
    print("2. After sleep")
    
    # Если нет await, Event Loop НЕ переключается:
    time.sleep(1)  # БЛОКИРУЕТ весь Event Loop!
    print("3. After blocking")

# ✅ Хорошо: используй await для всех асинхронных операций
async def fetch_multiple():
    tasks = [
        asyncio.create_task(fetch_from_api(url))
        for url in urls
    ]
    results = await asyncio.gather(*tasks)
    return results

# ❌ Плохо: блокирующие операции
async def bad_fetch():
    for url in urls:
        response = requests.get(url)  # БЛОКИРУЕТ Event Loop!

Асинхронные контекстные менеджеры

# ❌ Синхронно
with open('file.txt') as f:
    data = f.read()

# ✅ Асинхронно
import aiofiles

async def read_file():
    async with aiofiles.open('file.txt') as f:
        data = await f.read()
    return data

Event Loop в action (настоящий пример)

import asyncio
import aiohttp
from typing import List

async def fetch_user(session, user_id):
    async with session.get(f'https://jsonplaceholder.typicode.com/users/{user_id}') as resp:
        return await resp.json()

async def fetch_all_users(user_ids: List[int]):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_user(session, uid) for uid in user_ids]
        users = await asyncio.gather(*tasks)
    return users

# Запуск
result = asyncio.run(fetch_all_users([1, 2, 3, 4, 5]))
# Загружаем 5 пользователей за время одного запроса (~200-300ms)
# А не 5 * 200ms = 1 секунда!

Когда использовать async

  • Сетевые операции (HTTP, database, gRPC)
  • Файловые операции (очень медленно синхронно)
  • Много конкурентных задач (10+)
  • Нужна высокая пропускная способность

Когда НЕ использовать async

  • CPU-bound операции (нужен multiprocessing)
  • Мало задач (< 5)
  • Нет асинхронных библиотек для твоих нужд
  • Простое приложение (оверинжиниринг)

Production пример: веб-скрейпер

import asyncio
import aiohttp
from collections import defaultdict

class WebScraper:
    def __init__(self, max_concurrent=10):
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.results = []
    
    async def fetch(self, session, url):
        async with self.semaphore:  # Max 10 одновременных
            try:
                async with session.get(url, timeout=5) as resp:
                    return await resp.text()
            except Exception as e:
                print(f"Error fetching {url}: {e}")
                return None
    
    async def scrape_urls(self, urls):
        async with aiohttp.ClientSession() as session:
            tasks = [self.fetch(session, url) for url in urls]
            results = await asyncio.gather(*tasks, return_exceptions=True)
        return results

# Используем
scraper = WebScraper()
urls = [f'https://example.com/page/{i}' for i in range(100)]
results = asyncio.run(scraper.scrape_urls(urls))

Итог

Асинхронность в Python работает за счет:

  1. Event Loop — управляет переключением между задачами
  2. Корутины (async def) — функции которые можно паузировать
  3. await — точки, где Event Loop может переключиться
  4. Одноточность — все работает в одном потоке, но конкурентно

Это дает огромные преимущества для I/O операций, но требует асинхронных библиотек (aiohttp, asyncpg, aiofiles).

В production системах асинхронность критична для масштабируемости. Одно приложение на asyncio может обрабатывать 10,000+ одновременных соединений на одной машине.

За счёт чего реализуются асинхронные операции в Python? | PrepBro