← Назад к вопросам
Что произойдет с другими корутинами, если корутина 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
-
НИКОГДА не используй синхронный 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() -
Используй asyncio.to_thread для Python 3.9+
data = await asyncio.to_thread(slow_sync_function, arg1, arg2) -
Для сетевых операций используй асинхронные библиотеки
aiohttpвместоrequestsasyncpgвместоpsycopg2aioredisвместоredisaiofilesвместоopen
-
Профилируй event loop
loop = asyncio.get_event_loop() loop.set_debug(True) # Покажет долгие операции -
Помни: asyncio — кооперативный многозадачность
- Только
awaitпередает контроль - Синхронный код ВСЕГДА блокирует
- Вся идея asyncio в том, чтобы никогда не блокировать
- Только
Вывод: Синхронное чтение файла в asyncio корутине замораживает ВСЕ остальные корутины. Всегда используй асинхронный I/O или asyncio.to_thread.