За счёт чего реализуются асинхронные операции в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает асинхронность в 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())
Что происходит за кулисами:
- Event Loop запускается
- Вызывает
fetch_data(), который асинхронная функция (определена черезasync) - Когда встречает
await, он паузирует текущую задачу (не замораживает программу!) - Переключается на другую задачу, если она есть
- Когда операция завершена, возобновляет задачу
- Повторяет, пока все задачи не завершены
Ключевое: это НЕ многопоточность
# ❌ НЕПРАВИЛЬНО: это не параллелизм
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 работает за счет:
- Event Loop — управляет переключением между задачами
- Корутины (
async def) — функции которые можно паузировать - await — точки, где Event Loop может переключиться
- Одноточность — все работает в одном потоке, но конкурентно
Это дает огромные преимущества для I/O операций, но требует асинхронных библиотек (aiohttp, asyncpg, aiofiles).
В production системах асинхронность критична для масштабируемости. Одно приложение на asyncio может обрабатывать 10,000+ одновременных соединений на одной машине.