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

Что произойдет с другими корутинами, если корутина asyncio начнет читать большой файл через open('file').read()?

3.0 Senior🔥 281 комментариев
#DevOps и инфраструктура

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

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

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

asyncio блокировка при синхронном чтении файла

Это критическая проблема в asyncio приложениях. Синхронное чтение файла замораживает ВСЕ корутины.

Проблема: блокировка event loop

import asyncio

async def read_large_file():
    """❌ НЕПРАВИЛЬНО: блокирует весь event loop"""
    with open('huge_file.bin', 'rb') as f:
        data = f.read()  # БЛОКИРУЕТ! (может быть 1+ секунду)
    return len(data)

async def fast_operation():
    """Эта корутина ЗАВИСНЕТ, пока читаем файл"""
    print("Start")
    await asyncio.sleep(0.1)  # Хотим 0.1 сек
    print("Done")  # Но выведется ДО ТОГО как файл прочитается!

async def main():
    # Запускаем обе корутины параллельно
    results = await asyncio.gather(
        read_large_file(),
        fast_operation()
    )
    return results

# Время выполнения:
# read_large_file: 2 сек (читает файл)
# fast_operation: ЖДЕТ 2 сек (хотя нужно только 0.1)
# Итого: ~2 сек вместо параллельного выполнения

Что происходит в event loop

Время  Event Loop           read_large_file()    fast_operation()
----   -----------          -----------------    ----------------
0ms    В ожидании           Запущена
0ms    Начинает читать      f.read() БЛОКИРОВКА  ←― ждет своей очереди
0ms    Зависит!            ▓▓▓▓▓▓▓▓ (1000ms)    ←― остается заморожена
500ms  Все еще блокирован   ▓▓▓▓▓▓▓▓ (500ms)     ←― не может выполняться
1000ms Файл прочитан        ✓ Готово (1000ms)    ←― наконец может начать
1100ms fast_operation готов ✗ Но уже поздно      ✓ Готово (0.1ms)

Симптомы проблемы

import asyncio
import time

async def slow_sync_io():
    """Синхронное I/O"""
    print(f"[{time.time()}] Начинаю читать файл")
    data = open('huge_file.bin').read()  # Блокирует 2 секунды
    print(f"[{time.time()}] Закончил читать файл")
    return len(data)

async def ticker():
    """Должна печатать каждые 100ms"""
    for i in range(20):
        print(f"[{time.time()}] Tick {i}")
        await asyncio.sleep(0.1)  # Просто ждет

async def main():
    await asyncio.gather(
        slow_sync_io(),
        ticker()
    )

asyncio.run(main())

# Вывод (проблема очевидна):
# [0.0000] Начинаю читать файл
# [0.0001] Tick 0
# [0.0001] Tick 1
# [0.0001] Tick 2
# ...
# [0.0001] Tick 19
# [2.0000] Закончил читать файл
# (все Tick выполнились СРАЗУ без задержек!)

Решение 1: asyncio.to_thread (Python 3.9+)

import asyncio

async def read_large_file():
    """✅ ПРАВИЛЬНО: читаем в отдельном потоке"""
    # Помещаем синхронный I/O в отдельный поток
    data = await asyncio.to_thread(open('huge_file.bin', 'rb').read)
    return len(data)

async def fast_operation():
    """Эта корутина выполняется независимо"""
    print("Start")
    await asyncio.sleep(0.1)
    print("Done")  # Выводится в правильное время!

async def main():
    results = await asyncio.gather(
        read_large_file(),
        fast_operation()
    )
    return results

# Время выполнения:
# read_large_file: 2 сек (в фоновом потоке)
# fast_operation: 0.1 сек (параллельно!)
# Итого: ~2 сек (параллельно)

Решение 2: asyncio с ThreadPoolExecutor

import asyncio
from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(max_workers=2)

async def read_large_file():
    """Читаем файл в потоке из пула"""
    loop = asyncio.get_event_loop()
    
    def sync_read():
        with open('huge_file.bin', 'rb') as f:
            return f.read()
    
    # Выполняем синхронный код в потоке
    data = await loop.run_in_executor(executor, sync_read)
    return len(data)

async def main():
    result = await read_large_file()
    print(f"Read {result} bytes")

asyncio.run(main())

Решение 3: Асинхронные I/O библиотеки

import asyncio
import aiofiles  # pip install aiofiles

async def read_large_file():
    """✅ ПРАВИЛЬНО: используем асинхронное I/O"""
    async with aiofiles.open('huge_file.bin', 'rb') as f:
        data = await f.read()  # Не блокирует!
    return len(data)

async def fast_operation():
    print("Start")
    await asyncio.sleep(0.1)
    print("Done")  # Выполняется в правильное время

async def main():
    results = await asyncio.gather(
        read_large_file(),
        fast_operation()
    )
    return results

asyncio.run(main())

# Вывод:
# Start
# Done (после 0.1 сек, не дожидаясь чтения файла)

Решение 4: asyncio.run_in_executor с ProcessPoolExecutor

Для очень тяжелых вычислений:

import asyncio
from concurrent.futures import ProcessPoolExecutor

executor = ProcessPoolExecutor(max_workers=2)

def cpu_bound_read(filename):
    """CPU-bound операция в отдельном процессе"""
    with open(filename, 'rb') as f:
        data = f.read()
    # Может быть обработка данных
    return len(data)

async def read_with_processing():
    loop = asyncio.get_event_loop()
    # Используем процесс, не поток
    result = await loop.run_in_executor(
        executor,
        cpu_bound_read,
        'huge_file.bin'
    )
    return result

async def main():
    result = await read_with_processing()
    print(f"Processed {result} bytes")

Полное сравнение подходов

import asyncio
import time
import aiofiles
from concurrent.futures import ThreadPoolExecutor

# 1. ❌ НЕПРАВИЛЬНО: синхронный read
async def bad_approach():
    start = time.time()
    with open('huge_file.bin', 'rb') as f:
        data = f.read()  # БЛОКИРУЕТ event loop
    elapsed = time.time() - start
    return elapsed

# 2. ✅ asyncio.to_thread (рекомендуется для Python 3.9+)
async def good_approach_modern():
    start = time.time()
    data = await asyncio.to_thread(
        lambda: open('huge_file.bin', 'rb').read()
    )
    elapsed = time.time() - start
    return elapsed

# 3. ✅ run_in_executor
async def good_approach_executor():
    start = time.time()
    loop = asyncio.get_event_loop()
    executor = ThreadPoolExecutor(max_workers=1)
    
    def sync_read():
        with open('huge_file.bin', 'rb') as f:
            return f.read()
    
    data = await loop.run_in_executor(executor, sync_read)
    elapsed = time.time() - start
    return elapsed

# 4. ✅ Асинхронные библиотеки
async def good_approach_aiofiles():
    start = time.time()
    async with aiofiles.open('huge_file.bin', 'rb') as f:
        data = await f.read()
    elapsed = time.time() - start
    return elapsed

async def parallel_work():
    """Работа, которая должна выполняться параллельно"""
    for i in range(10):
        print(f"Work item {i}")
        await asyncio.sleep(0.1)

async def benchmark():
    # Запускаем параллельно file reading и work
    results = await asyncio.gather(
        good_approach_modern(),
        parallel_work()
    )
    print(f"Total time: {results[0]:.2f}s")

Практический пример: веб-сервер

from fastapi import FastAPI
from pathlib import Path
import aiofiles

app = FastAPI()

# ❌ НЕПРАВИЛЬНО: FastAPI работает в asyncio
@app.get("/bad/file")
async def download_file_bad():
    with open('huge_file.bin', 'rb') as f:
        data = f.read()  # Блокирует сервер!
    return {"size": len(data)}

# ✅ ПРАВИЛЬНО: асинхронное чтение
@app.get("/good/file")
async def download_file_good():
    async with aiofiles.open('huge_file.bin', 'rb') as f:
        data = await f.read()  # Не блокирует!
    return {"size": len(data)}

# ✅ ПРАВИЛЬНО: отдельный поток
@app.get("/file-thread")
async def download_file_thread():
    # Для очень больших файлов можно использовать поток
    data = await asyncio.to_thread(
        lambda: open('huge_file.bin', 'rb').read()
    )
    return {"size": len(data)}

Как выявить проблему

import asyncio
import logging

logging.basicConfig(level=logging.DEBUG)

# Дебаг: включи логирование asyncio
logging.getLogger('asyncio').setLevel(logging.DEBUG)

async def problem_function():
    # Если эта функция "зависнет",
    # все остальные корутины не будут выполняться
    data = open('huge_file.bin').read()  # ПРОБЛЕМА!
    return data

# Логи покажут долгие задержки и блокировки

Best Practices

  1. НИКОГДА не используй синхронный I/O в asyncio корутинах

    # ❌ Плохо
    data = open('file').read()
    
    # ✅ Хорошо
    data = await asyncio.to_thread(open, 'file').read()
    # или
    async with aiofiles.open('file') as f:
        data = await f.read()
    
  2. Используй asyncio.to_thread для Python 3.9+

    data = await asyncio.to_thread(slow_sync_function, arg1, arg2)
    
  3. Для сетевых операций используй асинхронные библиотеки

    • aiohttp вместо requests
    • asyncpg вместо psycopg2
    • aioredis вместо redis
    • aiofiles вместо open
  4. Профилируй event loop

    loop = asyncio.get_event_loop()
    loop.set_debug(True)  # Покажет долгие операции
    
  5. Помни: asyncio — кооперативный многозадачность

    • Только await передает контроль
    • Синхронный код ВСЕГДА блокирует
    • Вся идея asyncio в том, чтобы никогда не блокировать

Вывод: Синхронное чтение файла в asyncio корутине замораживает ВСЕ остальные корутины. Всегда используй асинхронный I/O или asyncio.to_thread.