Конкурентность против параллелизма

Урок разводит два часто путаемых понятия и объясняет, почему этот курс — про параллелизм.

Конкурентность — про структуру: как организовать программу из многих задач, которые продвигаются «вперемешку». Параллелизм — про исполнение: как заставить вычисления идти физически одновременно ради скорости.

Метафора повара

Один повар, который, ожидая закипания воды, режет овощи, а потом возвращается к плите — это конкурентность: он жонглирует несколькими задачами, но физически делает что-то одно в каждый момент. Два повара на одной кухне, готовящие два блюда одновременно — это параллелизм: в одну единицу времени реально выполняются две работы. Конкурентность возможна и на одном ядре (переключение между задачами), параллелизм требует нескольких вычислителей.

Зачем различать

Курс «Конкурентность и многопоточность» на этом сайте — про другое: про синхронизацию, гонки данных, блокировки, корректное разделение состояния между потоками. Там главный враг — ошибка корректности (две задачи испортили общую переменную). Здесь, в курсе про параллельные алгоритмы, главная цель — скорость: как разложить вычисление так, чтобы N устройств сократили время. Эти темы пересекаются (параллельный алгоритм часто использует потоки и должен избегать гонок), но угол зрения разный.

АспектКонкурентностьПараллелизм
Главный вопросКак структурировать много задач?Как ускорить одну задачу?
Нужно ли >1 ядраНетДа
Главный рискГонки, дедлокиМалое ускорение, дисбаланс
Типичный примерВеб-сервер на 1000 соединенийСложить миллион чисел на 8 ядрах

Когда они встречаются вместе

Веб-сервер, обрабатывающий тысячи соединений на восьмиядерном процессоре, и конкурентен (много задач в полёте), и параллелен (восемь ядер реально работают сразу). Но это два независимых свойства. Можно быть конкурентным без параллелизма (один поток с асинхронным циклом событий) и параллельным без сложной конкурентности (восемь ядер тупо суммируют свои куски, не разделяя состояние).

Различие не педантичное, а практическое: оно определяет, какой инструмент брать. Если ваша задача — обслуживать много медленных операций ввода-вывода (запросы к базе, сетевые вызовы), вам нужна конкурентность: пока одна операция ждёт ответа, поток занимается другой. Добавление ядер тут почти не поможет, потому что ядра всё равно простаивали бы в ожидании. Если же задача — перемолоть большой массив чисел, вам нужен параллелизм: разложить счёт на ядра. Конкурентность здесь не нужна вовсе, ведь нет ожидания, которое надо чем-то заполнять. Спутав эти случаи, легко потратить силы зря: накрутить асинхронность там, где надо было распараллелить счёт, или наоборот.

Как работает под капотом

Конкурентность реализуется планировщиком, который чередует задачи: дал поработать одной, сохранил её состояние, переключился на другую. На одном ядре это создаёт иллюзию одновременности. Параллелизм же опирается на аппаратуру: разные ядра имеют собственные регистры и АЛУ и действительно выполняют инструкции в один такт. Наша последовательная эмуляция в браузере показывает логику параллельного алгоритма, но реальное ускорение даёт только железо.

Маленькая модель «два повара»

tasks = ["суп", "салат", "десерт", "напиток"]

# конкурентно на 1 ядре: задачи идут по очереди, но "переключаемся"
print("=== 1 повар (конкурентно) ===")
for t in tasks:
    print("готовлю", t)

# параллельно на 2 поваров: раздаём по ролям (здесь печатаем раскладку)
print("=== 2 повара (параллельно) ===")
for i, t in enumerate(tasks):
    print("повар", i % 2 + 1, "->", t)

Вывод:

=== 1 повар (конкурентно) ===
готовлю суп
готовлю салат
готовлю десерт
готовлю напиток
=== 2 повара (параллельно) ===
повар 1 -> суп
повар 2 -> салат
повар 1 -> десерт
повар 2 -> напиток

Частые ошибки

  • Считать, что асинхронный код (один поток, event loop) «параллелен». Он конкурентен, но на одном ядре не ускоряет счёт.
  • Думать, что добавление потоков всегда даёт параллелизм. Если ядро одно, потоки лишь чередуются.
  • Переносить все приёмы из курса конкурентности сюда. Гонки важны, но цель здесь — ускорение, а не только корректность.

Итоги

  • Конкурентность — про структуру многих задач, параллелизм — про одновременное исполнение ради скорости.
  • Параллелизм требует нескольких вычислителей; конкурентность — нет.
  • Этот курс делает акцент на скорости: как спроектировать алгоритм под много ядер/GPU.
Проверьте себя
1. Чем параллелизм отличается от конкурентности?
AНичем, это синонимы
BПараллелизм — физически одновременное исполнение ради скорости; конкурентность — структура из многих задач
CКонкурентность всегда быстрее
DПараллелизм работает только на одном ядре
2. Может ли программа быть конкурентной, но не параллельной?
AНет, никогда
BДа: один поток с асинхронным циклом событий на одном ядре
CТолько на GPU
DТолько если есть гонки данных
3. На чём делает акцент именно этот курс?
AНа корректности синхронизации потоков
BНа скорости — как разложить вычисление на много устройств
CНа сетевых протоколах
DНа структурах данных для баз