Модуль 10: Конкурентность, производительность и упаковка
Понимание параллелизма и инструментов производительности важно для высоконагруженных и I/O‑интенсивных систем. В финале — как правильно оформлять и публиковать пакеты, и какие архитектурные подходы применять.
10.1 Asyncio и экосистема
asyncio
реализует кооперативную конкурентность на базе цикла событий.
import asyncio
async def fetch(i: int) -> str:
await asyncio.sleep(0.2)
return f"ok-{i}"
async def main() -> None:
tasks = [asyncio.create_task(fetch(i)) for i in range(5)]
done = await asyncio.gather(*tasks, return_exceptions=False)
print(done)
asyncio.run(main())
# Семафоры, ограничения параллелизма
import asyncio
async def worker(sem: asyncio.Semaphore, i: int) -> None:
async with sem:
await asyncio.sleep(0.1)
print(i)
async def bounded() -> None:
sem = asyncio.Semaphore(3)
await asyncio.gather(*(worker(sem, i) for i in range(10)))
asyncio.run(bounded())
Экосистема: aiohttp
(HTTP‑клиент/сервер), asyncpg
(PostgreSQL), aiokafka
, aiofiles
. Альтернативы: trio
, унификация через anyio
.
Не блокируйте цикл событий: для CPU‑задач используйте run_in_executor
или вынесите код в процессы.
10.2 GIL и модель памяти
GIL (Global Interpreter Lock) позволяет одновременно исполняться только одному байткоду Python в процессе. Это не мешает I/O‑параллелизму, но ограничивает CPU‑параллелизм с потоками.
# CPU‑bound задача эффективнее масштабировать процессами
from concurrent.futures import ProcessPoolExecutor
def fib(n: int) -> int:
return n if n < 2 else fib(n-1) + fib(n-2)
with ProcessPoolExecutor() as ex:
results = list(ex.map(fib, [28, 28, 28, 28]))
Модель памяти CPython гарантирует атомарность некоторых операций (например, инкремент счётчика не атомарен на уровне приложения). Для потоков используйте примитивы синхронизации: Lock
, Event
, Queue
.
10.3 Параллелизм и процессы
# multiprocessing
from multiprocessing import Pool, cpu_count
def square(x: int) -> int:
return x * x
with Pool(processes=cpu_count()) as pool:
print(pool.map(square, range(10)))
# concurrent.futures
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
urls = ["https://example.com"] * 10
with ThreadPoolExecutor(max_workers=5) as ex:
futures = [ex.submit(requests.get, u) for u in urls]
for fut in as_completed(futures):
print(fut.result().status_code)
Для межпроцессного обмена используйте multiprocessing.Queue
, Manager
, разделяемую память. При высоких нагрузках рассмотрите распределённые очереди (Redis
, RabbitMQ
).
10.4 Профилирование и оптимизация
# CPU‑профилирование
python -m cProfile -o stats.out app.py
python -m pstats stats.out
# Сэмплирующие профилировщики
py-spy record -o profile.svg -- python app.py
scalene app.py
# Трассировка времени участков кода
import time
from contextlib import contextmanager
@contextmanager
def timer(name: str):
t0 = time.perf_counter()
try:
yield
finally:
dt = time.perf_counter() - t0
print(f"{name}: {dt:.4f}s")
with timer("step"):
time.sleep(0.1)
- Оптимизируйте алгоритмы и структуры данных прежде чем микроптимизации.
- Используйте векторизацию (
numpy
) и C‑расширения (cython
,numba
) для горячих участков. - Измеряйте метрики памяти (
tracemalloc
), ищите утечки.
10.5 Упаковка и публикация
Современная упаковка стандартизована PEP 517/518 (build backend/pyproject.toml). Рекомендуемый стек: pip
+ build
+ twine
или poetry
.
# pyproject.toml (poetry)
[tool.poetry]
name = "awesome-lib"
version = "0.1.0"
description = "Пример пакета"
authors = ["You <you@example.com>"]
[tool.poetry.dependencies]
python = ">=3.10,<4.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
# Сборка и публикация (poetry)
poetry install
poetry build
poetry publish --username __token__ --password <PYPI_TOKEN>
# pyproject.toml (setuptools + build)
[build-system]
requires = ["setuptools>=68", "wheel", "build"]
build-backend = "setuptools.build_meta"
[project]
name = "awesome-lib"
version = "0.1.0"
dependencies = ["requests>=2"]
# Сборка и публикация (build + twine)
python -m build
python -m twine upload dist/*
Храните версии в одном источнике. Закрепляйте зависимости (poetry.lock
/requirements.txt
). Следуйте семантическому версионированию.
10.6 Архитектурные практики
- Слои: разделяйте домен, приложение, инфраструктуру. Ввод/вывод изолируйте в адаптерах.
- Чистые функции и явные побочные эффекты: облегчает тестирование.
- Зависимости: инвертируйте зависимости через интерфейсы/протоколы.
- Конфигурация: 12‑factor, окружение/файл/переменные.
- Логи/метрики/трейсинг: observability по умолчанию.
- Документация: README, ADR, type hints как живая документация.