Files
2026-10-07 13:06:47 +05:00

130 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Лабораторная работа 6. Асинхронный код (asyncio)
**Время на работу:** 2 пары. **Зависимости:** лабы 1–2.
## Цель работы
Освоить кооперативную многозадачность: `async/await`, event loop, `gather`,
ограничение конкурентности семафором. Понять, чем asyncio отличается от
потоков и когда выбирать её.
## Теория
### Модель: один поток, но тысячи задач
Asyncio — **однопоточная** кооперативная многозадачность. Есть один цикл
событий (event loop) и множество корутин. Корутина выполняется, пока сама
не уступит управление через `await` — это ключевое отличие от потоков, где
ОС переключает задачи принудительно в любой момент (вытесняющая
многозадачность).
Следствия:
- нет гонок на `counter += 1` между await-ами: код между await-ами
исполняется атомарно (переключение — только на await);
- зато «жадная» корутина без await блокирует всё;
- одна корутина может ожидать тысячи сокетов — накладные расходы на задачу
в разы меньше, чем на поток.
### Ключевые слова и функции
```python
async def fetch(i): # корутина — «ленивая» функция
await asyncio.sleep(0.05) # уступает управление на время ожидания
return i
async def main():
results = await asyncio.gather( # запустить все и ждать все
fetch(1), fetch(2), fetch(3))
task = asyncio.create_task(fetch(4)) # задача: стартовала сейчас
r = await task # ...а результат ждём тут
```
`asyncio.run(main())` — вход в async-мир из обычного кода (один раз на
программу). `await` можно ставить только внутри `async def`.
### gather и семафор
`gather` запускает все корутины конкурентно. Если задач тысячи, а «сервер»
выдерживает 100 соединений — используем `asyncio.Semaphore`:
```python
sem = asyncio.Semaphore(100)
async def limited(i):
async with sem:
return await fetch(i)
```
### Потоки или asyncio?
| | Потоки | asyncio |
|---|---|---|
| Переключение | вытесняющее (ОС) | кооперативное (await) |
| Масштаб задач | десятки–сотни | тысячи и больше |
| Гонки данных | да | только между await-ами |
| CPU-код | бесполезен (GIL) | блокирует всё |
| Блокирующие библиотеки | подходят | блокируют цикл (!) |
Критерий: много I/O + нужно много одновременных задач → asyncio.
CPU-работа → процессы (лаба 4). Смешанная → комбинировать.
### Чем опасна блокирующая функция в async-коде
`time.sleep(0.05)` (не `asyncio.sleep`) внутри корутины останавливает ВЕСЬ
цикл событий: ни одна другая корутина не выполняется. В работе это
демонстрируется явно.
## Задание
Заполните `solution.py` (все функции `async`, кроме `main`):
1. **`fetch_async(item, delay)`** — корутина: `await asyncio.sleep(delay)`,
вернуть `(item, item * item)`.
2. **`fetch_all_async(items, delay)`** — конкурентно все items запросов
через `asyncio.gather`. Вернуть `(результаты, сек)`, упорядочены по item.
3. **`fetch_all_limited(items, delay, max_concurrent)`** — то же, но
не более max_concurrent одновременно (`asyncio.Semaphore`).
4. **`measure_blocking_mistake(items, delay)`** — «ошибка новичка»:
корутина использует `time.sleep(delay)` вместо `asyncio.sleep`.
Вернуть время выполнения: оно окажется ≈ items × delay — блокирующий
сон парализует цикл событий.
5. **`main()`** — все пункты + сравнение с потоками (лаба 2).
### Ожидаемые результаты
- `fetch_all_async(96, 0.05)` ≈ 0.05–0.1 c — все запросы «одновременно».
- `fetch_all_limited(96, 0.05, 8)` ≈ 0.6 c.
- `measure_blocking_mistake(96, 0.05)` ≈ 4.8 c — блокирующий sleep убил
конкурентность.
## Контрольные вопросы
1. Чем кооперативная многозадачность отличается от вытесняющей?
2. Почему между двумя `await` корутина «владеет» данными без Lock?
3. Что случится, если в корутине вызвать `time.sleep(10)`? Проверьте.
4. Когда выбрали бы потоки, а когда asyncio? Приведите по примеру.
5. Что делает `asyncio.gather` и чем отличается от последовательных `await`?
## Что сдаётся
`solution.py` + отчёт: таблица сравнения (последовательно/потоки/async/
async+лимит/ошибка с time.sleep), анализ, ответы на вопросы.
## Критерии оценки
| Пункт | Баллы |
|---|---|
| fetch_all_async: время ≈ delay, результаты упорядочены | 2 |
| fetch_all_limited: лимит соблюдается | 2 |
| measure_blocking_mistake: продемонстрирована деградация | 2 |
| Сравнительная таблица в отчёте | 2 |
| Ответы на контрольные вопросы | 2 |
## Типичные ошибки
- `await` вне `async def` — SyntaxError.
- Забыт `await` перед корутиной: `RuntimeWarning: never awaited`, ничего
не выполняется.
- `asyncio.run()` внутри работающего цикла (Jupyter) — RuntimeError.
- `time.sleep` в корутине — «секретный» последовательный режим (это п.4
работы, тут это фича).