В чем разница между блокирующим и неблокирующим ввод-выводом?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Блокирующий vs неблокирующий I/O
Одно из ключевых различий в моделях работы с вводом-выводом в программировании, которое сильно влияет на производительность и архитектуру приложений.
Блокирующий I/O (Blocking)
Программа ждет завершения операции ввода-вывода, прежде чем продолжить работу.
import socket
import time
# Блокирующий сокет (по умолчанию)
socket_blocking = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_blocking.connect(('example.com', 80))
# Программа ждет, пока данные придут
data = socket_blocking.recv(1024) # БЛОКИРУЕТ здесь
print(f"Получено: {data}")
# Продолжит работу только когда данные получены
print("Это выполнится после recv()")
Проблема: если сервер медленный, программа зависнет на этой строке.
Неблокирующий I/O (Non-blocking)
Операция ввода-вывода возвращает результат немедленно, даже если данные еще не готовы.
import socket
# Переводим сокет в неблокирующий режим
socket_nonblocking = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_nonblocking.setblocking(False)
try:
socket_nonblocking.connect(('example.com', 80))
except BlockingIOError:
# Это нормально для неблокирующего режима
print("Соединение в процессе...")
# Попытка прочитать данные
try:
data = socket_nonblocking.recv(1024) # Вернет пусто, если нет данных
print(f"Получено: {data}")
except BlockingIOError:
print("Данные еще не готовы")
# Программа продолжит работу СРАЗУ
print("Это выполнится немедленно")
Асинхронный I/O (Async) — лучшее из двух миров
Сочетает удобство блокирующего кода с эффективностью неблокирующего.
import asyncio
import aiohttp
async def fetch_url(url: str) -> str:
# Это выглядит как блокирующий код
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text() # ЖДЕТ, но не блокирует!
async def main():
# Запустим несколько запросов параллельно
urls = [
'https://api.example.com/1',
'https://api.example.com/2',
'https://api.example.com/3',
]
# Создаем задачи
tasks = [fetch_url(url) for url in urls]
# Выполняем все параллельно
results = await asyncio.gather(*tasks)
for i, result in enumerate(results):
print(f"Результат {i}: {len(result)} символов")
# Запуск
asyncio.run(main())
Сравнение подходов
| Подход | Скорость | Сложность | Использование |
|---|---|---|---|
| Блокирующий | Медленный (~3 сек для 3 запросов) | Простой | I/O операции редкие |
| Многопоточность | Средний (параллелизм ОС) | Средняя | 10-100 задач |
| Неблокирующий | Быстрый | Сложный (callback hell) | Редко |
| Асинхронный (async/await) | Быстрый (~1 сек для 3 запросов) | Средняя | Масштабируемые сервисы |
Практический пример: веб-скрейпер
Блокирующий вариант (медленный)
import requests
import time
def scrape_blocking(urls: list[str]) -> list[str]:
results = []
start = time.time()
for url in urls:
# Ждет ответа КАЖДЫЙ раз
response = requests.get(url)
results.append(response.text)
print(f"Время: {time.time() - start:.1f}сек")
return results
# Для 10 URL по 1 сек каждый: 10 секунд
scrape_blocking(['http://api.example.com/'] * 10)
Асинхронный вариант (быстрый)
import asyncio
import aiohttp
import time
async def scrape_async(urls: list[str]) -> list[str]:
results = []
start = time.time()
async with aiohttp.ClientSession() as session:
# Создаем все задачи параллельно
tasks = [session.get(url) for url in urls]
# Выполняем все одновременно
responses = await asyncio.gather(*tasks)
for response in responses:
text = await response.text()
results.append(text)
print(f"Время: {time.time() - start:.1f}сек")
return results
# Для 10 URL: ~1 сек (все параллельно)
asyncio.run(scrape_async(['http://api.example.com/'] * 10))
Применение в реальных проектах
FastAPI (асинхронный веб-фреймворк)
from fastapi import FastAPI
import aiohttp
app = FastAPI()
@app.get('/users/{user_id}')
async def get_user(user_id: int):
# Асинхронный запрос к БД (не блокирует другие запросы)
async with aiohttp.ClientSession() as session:
async with session.get(f'https://api.example.com/users/{user_id}') as response:
return await response.json()
# Один сервер может обработать ТЫСЯЧИ одновременных запросов
# с асинхронным кодом против СОТЕН с потоками
Запросы к БД
# SQLAlchemy Async (неблокирующий)
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.future import select
async def get_users():
engine = create_async_engine('postgresql+asyncpg://...')
async with engine.begin() as conn:
result = await conn.execute(select(User))
users = result.scalars().all()
# Не блокирует другие операции
return users
Выбор в зависимости от задачи
- Блокирующий — для синхронного кода, простых скриптов, если нет параллельности
- Многопоточность — для CPU-bound задач, когда нужен параллелизм ОС
- Асинхронный — для I/O-bound операций: сетевые запросы, БД, файлы
# Когда использовать async
async def handle_request():
# Множество сетевых операций
user = await db.get_user(id)
profile = await cache.get(user.id)
permissions = await auth_service.check(user.id)
return {user, profile, permissions}
# Когда использовать threading
from concurrent.futures import ThreadPoolExecutor
def compute_heavy():
# CPU-bound работа
return sum(i**2 for i in range(10000000))
with ThreadPoolExecutor() as executor:
results = executor.map(compute_heavy, range(10))
Вывод
Неблокирующий асинхронный I/O — это стандарт для современных веб-приложений на Python. Он позволяет одному процессу обрабатывать тысячи одновременных соединений, что невозможно с блокирующим кодом.