← Назад к вопросам
Какие знаешь особенности работы с асинхронным программированием?
1.7 Middle🔥 181 комментариев
#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие знаешь особенности работы с асинхронным программированием?
Асинхронное программирование (asyncio) — один из самых мощных инструментов в Python для обработки I/O-bound операций. Но оно требует кардинально другого мышления.
1. Основной концепт: Корутины и Event Loop
Корутина — это функция, которая может приостановиться (await), пока ждет I/O, позволяя другим корутинам выполняться.
import asyncio
# Обычная функция
def sync_hello():
print("Hello")
time.sleep(1) # Блокирует ВСЁ
print("World")
# Асинхронная функция (корутина)
async def async_hello():
print("Hello")
await asyncio.sleep(1) # Не блокирует другие корутины!
print("World")
# Event Loop — главный диспетчер, который управляет корутинами
async def main():
# Запустить две корутины параллельно
await asyncio.gather(
async_hello(), # Напечатает в 0s, 1s
async_hello(), # Напечатает в 0s, 1s
)
# Всего времени: 1 секунда (не 2!)
asyncio.run(main())
# Output:
# Hello
# Hello
# (wait 1 second)
# World
# World
2. Event Loop как сердце асинхронности
Event Loop — это главный цикл, который:
- Запускает готовые корутины
- Проверяет завершенные операции I/O
- Просыпает заснувшие корутины
- Повторяет в цикле
import asyncio
async def worker(name, delay):
print(f"{name} started")
await asyncio.sleep(delay) # Корутина спит, но Event Loop работает с другими
print(f"{name} finished")
async def main():
# Создаем 3 корутины
tasks = [
asyncio.create_task(worker("Task 1", 3)),
asyncio.create_task(worker("Task 2", 1)),
asyncio.create_task(worker("Task 3", 2)),
]
# Event Loop управляет всеми одновременно
await asyncio.gather(*tasks)
asyncio.run(main())
# Output (в порядке завершения):
# Task 1 started
# Task 2 started
# Task 3 started
# Task 2 finished (1s)
# Task 3 finished (2s)
# Task 1 finished (3s)
# Total: 3s (а не 6s!)
3. Async/Await синтаксис
async определяет корутину, await останавливает выполнение:
import asyncio
import aiohttp
# Неправильно: обычная функция, не корутина
def fetch_wrong():
response = requests.get("https://api.example.com") # БЛОКИРУЕТ!
return response.json()
# Правильно: асинхронная функция
async def fetch_correct():
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com") as response:
return await response.json() # Не блокирует!
async def main():
# await может быть только в async функции
result = await fetch_correct()
print(result)
asyncio.run(main())
# Ошибка: нельзя использовать await вне async функции
# await fetch_correct() # SyntaxError!
# Нужно запустить через Event Loop
asyncio.run(fetch_correct())
4. Tasks и Futures
Task — это обертка вокруг корутины, которая позволяет запустить ее асинхронно:
import asyncio
async def fetch_data(url):
await asyncio.sleep(1)
return f"Data from {url}"
async def main():
# Способ 1: await (ждет выполнения)
result = await fetch_data("https://api.example.com")
print(result) # Через 1 секунду
# Способ 2: create_task (запустить и не ждать сразу)
task1 = asyncio.create_task(fetch_data("https://api1.example.com"))
task2 = asyncio.create_task(fetch_data("https://api2.example.com"))
print("Tasks created, not waiting yet")
# Позже ждем оба
result1 = await task1
result2 = await task2
print(result1, result2)
# Способ 3: gather (самый удобный для нескольких корутин)
results = await asyncio.gather(
fetch_data("https://api1.example.com"),
fetch_data("https://api2.example.com"),
fetch_data("https://api3.example.com"),
)
print(results) # Все 3 параллельно за 1 секунду
5. Важная особенность: Зависание (Blocking Calls)
Самая частая ошибка — использование синхронного кода в async функции:
import asyncio
import requests # Синхронная библиотека
import aiohttp # Асинхронная библиотека
# НЕПРАВИЛЬНО: это блокирует Event Loop!
async def bad_fetch():
response = requests.get("https://api.example.com") # БЛОКИРУЕТ ВСЁ!
return response.json()
async def main():
# Хотя это async функция, requests.get заблокирует Event Loop
# На время выполнения requests.get другие корутины не будут выполняться
result = await bad_fetch()
# ПРАВИЛЬНО: используй асинхронные версии
async def good_fetch():
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com") as response:
return await response.json()
async def better_fetch_with_blocking():
# Если нужно использовать синхронный код, запусти его в thread pool
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, requests.get, "https://api.example.com")
return result.json()
6. Таймауты и Отмена
import asyncio
async def slow_operation():
await asyncio.sleep(10)
return "Done"
async def main():
# Таймаут: отмени задачу если она не закончится за 2 секунды
try:
result = await asyncio.wait_for(slow_operation(), timeout=2)
except asyncio.TimeoutError:
print("Operation timed out!")
# Отмена задачи
task = asyncio.create_task(slow_operation())
await asyncio.sleep(1)
task.cancel() # Отменить задачу
try:
await task
except asyncio.CancelledError:
print("Task was cancelled")
7. Race Conditions в асинхронности
Даже в async коде есть race conditions, но они другого типа:
import asyncio
counter = 0
async def increment_bad():
"""Имеет race condition, даже в async!"""
global counter
# Эти строки НЕ атомарные в Python:
# 1. Прочитать counter
# 2. Добавить 1
# 3. Записать обратно
# Другая корутина может выполниться между ними!
counter += 1
async def main():
tasks = [increment_bad() for _ in range(100)]
await asyncio.gather(*tasks)
print(f"Counter: {counter}") # < 100 (race condition!)
asyncio.run(main())
# Правильно: используй Lock
lock = asyncio.Lock()
async def increment_good():
global counter
async with lock: # Гарантирует эксклюзивный доступ
counter += 1
async def main():
counter = 0
tasks = [increment_good() for _ in range(100)]
await asyncio.gather(*tasks)
print(f"Counter: {counter}") # = 100
8. Иерархия корутин и исключения
Исключения в корутинах требуют особого внимания:
import asyncio
async def failing_task():
await asyncio.sleep(0.1)
raise ValueError("Something went wrong!")
async def main():
# Если не обработать исключение, оно будет проигнорировано!
task = asyncio.create_task(failing_task())
# Другой код продолжит выполняться
await asyncio.sleep(0.2)
# Исключение выбросится только при await
try:
await task
except ValueError as e:
print(f"Caught: {e}")
# Правильно: обработать исключение в gather
try:
await asyncio.gather(
failing_task(),
asyncio.sleep(1),
)
except ValueError as e:
print(f"Error in gather: {e}")
9. Практический пример: Параллельные HTTP запросы
import asyncio
import aiohttp
async def fetch_url(session, url):
"""Асинхронно получить содержимое URL"""
async with session.get(url) as response:
return await response.text()
async def fetch_multiple(urls):
"""Получить несколько URL параллельно"""
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
return await asyncio.gather(*tasks)
async def main():
urls = [
"https://example.com",
"https://google.com",
"https://github.com",
]
results = await fetch_multiple(urls)
for url, content in zip(urls, results):
print(f"{url}: {len(content)} bytes")
asyncio.run(main())
# Output:
# https://example.com: 1256 bytes
# https://google.com: 12453 bytes
# https://github.com: 54321 bytes
# Total time: ~1 second (параллельно!)
# Если последовательно: ~3 seconds
10. Таблица Async vs Sync
| Аспект | Sync | Async |
|---|---|---|
| Простота | ✅ Проста | ❌ Сложнее |
| I/O-bound | ❌ Медленно | ✅ Быстро |
| CPU-bound | ✅ Окей | ❌ Не подходит |
| Конкурентность | До ~100 потоков | Тысячи задач |
| Синхронизация | Lock, RLock | asyncio.Lock |
| Библиотеки | requests, pandas | aiohttp, asyncpg |
| Debug | ✅ Проще | ❌ Сложнее |
11. Частые ошибки
# ❌ Ошибка 1: забыли await
async def main():
task = fetch_url("https://example.com") # Не выполнится!
# Нужно: await fetch_url(...)
# ❌ Ошибка 2: смешать sync и async
async def main():
# requests — синхронный, блокирует Event Loop
response = requests.get("https://example.com") # Плохо!
# Нужно: async with aiohttp.ClientSession() ...
# ❌ Ошибка 3: забыли asyncio.create_task для параллельного выполнения
async def main():
result1 = await fetch_url("url1") # Ждет 1
result2 = await fetch_url("url2") # Ждет 2 (последовательно!)
# Нужно:
task1 = asyncio.create_task(fetch_url("url1"))
task2 = asyncio.create_task(fetch_url("url2"))
r1, r2 = await asyncio.gather(task1, task2) # Параллельно!
# ❌ Ошибка 4: callback hell с .then()
# asyncio не имеет promise-style .then(), используй await вместо этого