115 lines
4.6 KiB
Python
115 lines
4.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Лабораторная работа 6: асинхронный код (asyncio).
|
||
|
||
Заполните функции ниже. ИНТЕРФЕЙСЫ МЕНЯТЬ НЕЛЬЗЯ — по ним работают
|
||
автотесты (tests/test_lab06.py).
|
||
|
||
Запуск из корня курса:
|
||
python lab06_asyncio/solution.py
|
||
"""
|
||
import asyncio
|
||
import os
|
||
import sys
|
||
import time
|
||
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||
|
||
from common.benchmark import make_table
|
||
from common.tasks import get_variant
|
||
|
||
# TODO: впишите свой номер в журнале (1..20)
|
||
VARIANT_NUMBER = 0
|
||
|
||
|
||
async def fetch_async(item: int, delay: float):
|
||
"""Корутина: имитация I/O-запроса.
|
||
|
||
await asyncio.sleep(delay) — НЕ блокирует цикл событий.
|
||
Вернуть (item, item * item).
|
||
"""
|
||
# TODO: asyncio.sleep + вернуть результат
|
||
raise NotImplementedError
|
||
|
||
|
||
async def fetch_all_async(items: int, delay: float):
|
||
"""Все items запросов конкурентно через asyncio.gather.
|
||
|
||
Вернуть (список результатов, elapsed_seconds), упорядочен по item.
|
||
"""
|
||
# TODO: gather по всем item -> сортировка не нужна, gather сохраняет порядок
|
||
raise NotImplementedError
|
||
|
||
|
||
async def fetch_all_limited(items: int, delay: float, max_concurrent: int):
|
||
"""То же, но не более max_concurrent одновременно (asyncio.Semaphore).
|
||
|
||
Вернуть (список результатов, elapsed_seconds), упорядочен по item.
|
||
"""
|
||
# TODO: sem = asyncio.Semaphore(...); async with sem: await fetch_async(...)
|
||
raise NotImplementedError
|
||
|
||
|
||
async def _fetch_blocking(item: int, delay: float):
|
||
"""«Ошибка новичка»: блокирующий time.sleep внутри корутины.
|
||
|
||
time.sleep НЕ уступает управление — парализует весь event loop.
|
||
"""
|
||
time.sleep(delay)
|
||
return (item, item * item)
|
||
|
||
|
||
async def _blocking_mistake(items: int, delay: float):
|
||
"""Все запросы через gather, но с блокирующим сном. Вернуть секунды."""
|
||
# TODO: gather по _fetch_blocking, вернуть время
|
||
raise NotImplementedError
|
||
|
||
|
||
def measure_blocking_mistake(items: int, delay: float) -> float:
|
||
"""Обёртка для тестов: asyncio.run + return секунд."""
|
||
# TODO: return asyncio.run(_blocking_mistake(items, delay))
|
||
raise NotImplementedError
|
||
|
||
|
||
def main():
|
||
v = get_variant(VARIANT_NUMBER)
|
||
items, delay = v["io"]["items"], v["io"]["delay"]
|
||
print(f"Вариант {v['variant']}: I/O-нагрузка {items} запросов по {delay} c\n")
|
||
|
||
# --- 1. Async: все запросы сразу ---------------------------------------
|
||
t0 = time.perf_counter()
|
||
results, t_async = asyncio.run(fetch_all_async(items, delay))
|
||
print(f"async, без лимита: {t_async:.3f} c "
|
||
f"(упорядочено: {[r[0] for r in results] == list(range(items))})")
|
||
|
||
# --- 2. Async с лимитом --------------------------------------------------
|
||
for mc in (4, 8):
|
||
res, t_lim = asyncio.run(fetch_all_limited(items, delay, mc))
|
||
print(f"async, лимит {mc}: {t_lim:.3f} c "
|
||
f"(идеал ≈ {items / mc * delay:.2f} c)")
|
||
|
||
# --- 3. Ошибка: time.sleep в корутине -----------------------------------
|
||
t_block = measure_blocking_mistake(items, delay)
|
||
print(f"async + time.sleep (ошибка!): {t_block:.3f} c "
|
||
f"≈ последовательные {items * delay:.2f} c\n")
|
||
|
||
# --- 4. Сравнение с потоками (лаба 2) ------------------------------------
|
||
from concurrent.futures import ThreadPoolExecutor
|
||
from common.tasks import io_fetch
|
||
|
||
t0 = time.perf_counter()
|
||
with ThreadPoolExecutor(max_workers=8) as pool:
|
||
list(pool.map(io_fetch, range(items), [delay] * items))
|
||
t_threads = time.perf_counter() - t0
|
||
|
||
rows = [("async без лимита", t_async),
|
||
("async, лимит 8", t_lim),
|
||
("потоки, p=8", t_threads),
|
||
("последовательно (ошибка time.sleep)", t_block)]
|
||
print(make_table(rows, title="Сравнение подходов к I/O"))
|
||
|
||
# TODO: в отчёт — таблицу, объяснение разницы потоков и asyncio,
|
||
# ответ на вопрос «почему time.sleep убил конкурентность».
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |