Python

Модуль 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 как живая документация.

Настройки

Цветовая схема

Тема