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

В чем разница между блокирующим и неблокирующим ввод-выводом?

1.8 Middle🔥 181 комментариев
#Асинхронность и многопоточность

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

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

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

Блокирующий 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

Выбор в зависимости от задачи

  1. Блокирующий — для синхронного кода, простых скриптов, если нет параллельности
  2. Многопоточность — для CPU-bound задач, когда нужен параллелизм ОС
  3. Асинхронный — для 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. Он позволяет одному процессу обрабатывать тысячи одновременных соединений, что невозможно с блокирующим кодом.

В чем разница между блокирующим и неблокирующим ввод-выводом? | PrepBro