Опишите основные подходы к конкурентности в Python и их типичное применение.

«Опишите основные подходы к конкурентности в Python и их типичное применение.» — вопрос из категории Асинхронность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Python существует несколько основных подходов к конкурентности, каждый из которых оптимален для разных типов задач:

  1. Потоки (threading)

    • Применение: Идеальны для I/O-bound задач, где выполнение блокируется ожиданием внешних операций (например, сетевые запросы, чтение/запись файлов). Python GIL (Global Interpreter Lock) ограничивает параллельное выполнение CPU-bound кода в потоках, но для I/O-bound задач потоки эффективны, так как GIL освобождается во время ожидания.
      
      import threading
      import requests

    def fetch_url(url): print(f"Fetching {url}...") response = requests.get(url) print(f"Finished {url} with status {response.status_code}")

    urls = [ "https://www.google.com", "https://www.yandex.ru", "https://www.bing.com" ]

    threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls] [t.start() for t in threads] # Запуск всех потоков [t.join() for t in threads] # Ожидание завершения всех потоков

  2. Процессы (multiprocessing)

    • Применение: Используются для CPU-bound задач, требующих интенсивных вычислений (например, обработка изображений, сложные математические расчеты). Каждый процесс имеет свой интерпретатор Python и собственное адресное пространство, что позволяет обходить ограничение GIL и использовать все ядра процессора для истинного параллелизма.
      
      from multiprocessing import Pool
      import time

    def heavy_computation(n):

    Имитация тяжелых вычислений

    sum_val = 0
    for i in range(n):
        sum_val += i * i
    return sum_val

    if name == 'main': numbers = [107, 107, 107, 107] with Pool() as p: results = p.map(heavy_computation, numbers) print(f"Results: {results}")

  3. Асинхронность (asyncio)

    • Применение: Подход для высоконагруженных I/O-bound задач, основанный на кооперативной многозадачности и одном потоке выполнения. Позволяет эффективно обрабатывать множество одновременных операций ввода-вывода без создания большого количества потоков или процессов, используя await для неблокирующего ожидания. Идеально для сетевых приложений (веб-серверы, WebSocket, API-клиенты).
      
      import asyncio
      import aiohttp

    async def fetch_async(url): print(f"Async fetching {url}...") async with aiohttp.ClientSession() as session: async with session.get(url) as response: print(f"Async finished {url} with status {response.status}") return await response.text()

    async def main_async(): urls = [ "https://www.google.com", "https://www.yandex.ru", "https://www.bing.com" ] await asyncio.gather(*[fetch_async(url) for url in urls])

    if name == 'main': asyncio.run(main_async())

Дополнительные аспекты:

  • GIL (Global Interpreter Lock): Ключевое ограничение CPython, которое позволяет выполнять только один поток байт-кода Python за раз. Для CPU-bound задач это означает, что threading не дает истинного параллелизма. Обходится использованием multiprocessing или C-расширений.
  • Синхронизация: Для координации доступа к общим ресурсам в конкурентных программах используются примитивы синхронизации: Lock, Semaphore, Event, Queuethreading и multiprocessing), а также asyncio.Lock, asyncio.Semaphore и asyncio.Queue для асинхронного кода.
  • Выбор подхода: Определяется типом задачи (CPU-bound vs. I/O-bound), требованиями к производительности, сложности реализации и потреблением ресурсов.