Что такое конкурентность в программировании и чем она отличается от параллелизма?

Ответ

Это два подхода к выполнению нескольких задач, которые часто путают. Ключевое различие — в способе их выполнения.

Конкурентность (Concurrency)

Конкурентность — это управление множеством задач в перекрывающиеся промежутки времени. Задачи могут выполняться в одном потоке, который быстро переключается между ними, создавая иллюзию одновременности.

  • Аналогия: Повар, который одновременно готовит суп и салат. Он ставит суп вариться (I/O-операция), а пока тот готовится, нарезает овощи для салата. Он не делает два дела в один и тот же момент, а эффективно переключается между ними.
  • Цель: Максимально утилизировать ресурсы во время ожидания (например, ответа от сети, чтения файла).

Параллелизм (Parallelism)

Параллелизм — это одновременное выполнение нескольких задач. Это возможно только в системе с несколькими ядрами процессора, где каждая задача может выполняться на своем ядре.

  • Аналогия: Два повара, один из которых готовит суп, а второй — салат. Обе задачи выполняются в один и тот же момент времени независимо друг от друга.
  • Цель: Ускорить вычисления, разделив одну большую задачу на подзадачи (CPU-bound).

Реализация в Python

Из-за GIL (Global Interpreter Lock) в CPython настоящий параллелизм для потоков (threading) затруднен. Поэтому выбор инструмента зависит от типа задачи:

  • asyncio: Идеально для конкурентного выполнения большого количества I/O-bound задач (сетевые запросы, работа с файлами) в одном потоке.

    import asyncio
    
    async def fetch_data(url):
      print(f"Fetching {url}...")
      await asyncio.sleep(1) # Имитация сетевого запроса
      print(f"Fetched {url}")
      return { "data": url }
    
    async def main():
      # Задачи запускаются конкурентно, а не последовательно
      await asyncio.gather(
          fetch_data("url_1"),
          fetch_data("url_2")
      )
    
    asyncio.run(main())
  • threading: Классический подход для I/O-bound задач. Потоки операционной системы, но GIL мешает параллелизму для CPU-задач.
  • multiprocessing: Единственный способ достичь настоящего параллелизма для CPU-bound задач (сложные вычисления, обработка данных), так как каждый процесс имеет свой собственный интерпретатор Python и память, обходя GIL.