Многозадачность и параллелизм: руководство для начинающих
Возможно, пока это изображение для вас не имеет смысла — в дальнейшем все поймете

Полагаю, что многозадачность и параллелизм относятся к самым непонятным терминам, входящим в основы информатики. Тем не менее они составляют одну из важнейших концепций программирования, без понимания которой невозможно написание эффективного кода во многих проектах.

Что такое процесс и потоки?

Нельзя приступить к рассмотрению вышеперечисленных терминов, не разобравшись в таких фундаментальных понятиях, как процесс и потоки.

Процесс

Изображение, знакомое вам по учебникам информатики

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

Таким образом, процесс  —  это изолированное пространство (с зарезервированной памятью и вычислительной мощностью) в ОС, где могут выполняться такие программы, как Chrome, VSCode, Slack и т. д.

Поток

За неимением удачного технического изображения потока, воспользуемся метафорическим образом потока в виде нити

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

Процесс может иметь много потоков, но каждый поток принадлежит только одному процессу.

Простая аналогия

Типичная структура компании

Попробую объяснить два вышеуказанных понятия с помощью простой аналогии. Допустим, есть некая компания, которой нужно управлять. В ней есть три отдела: технологический (Technology), маркетинговый (Marketing) и отдел кадров (HR).

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

  • Рабочее пространство в офисе -> аналогично памяти в ОС.
  • Работники/рабочая сила -> аналогично потокам в процессе.

Таким образом, отдельные люди (потоки) выполняют задачи, реализуемые в рамках отдела (процесса).

Многозадачность

Вы наверняка слышали старую поговорку:

Нельзя делать две вещи одновременно.

И это правда, то есть можно, но в итоге телефон окажется в холодильнике, а молоко  —  на подставке для зарядки. То же самое верно и для компьютера.

Один поток может выполнять только одну задачу за раз. Но это утверждение не соответствует истине, поскольку на одноядерном процессоре может быть открыто сразу несколько программ. Это достигается за счет многозадачности.

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

Многозадачность в действии, проиллюстрированная с помощью async/await. Источник

Возьмем для примера приведенное выше изображение. Зеленая линия представляет поток, выполняющий работу. В функции async, когда поток доходит до части await, происходит следующее:

  • Поток ожидает, пока не завершится действие http.get(). Он будет ожидать, пока не будет получен результат в переменной content.
  • Однако это не означает, что поток будет заблокирован только на http.get(). Он будет продолжать переключаться между http.get() и другими задачами в приложении.
  • Если бы ключевого слова await не было, то поток не стал бы ждать результата и просто перешел бы на следующую строку.

Во всем этом процессе только один поток пытался выполнить различные задачи приложения. Его часто называют циклом событий или основным потоком/тредом.

Параллелизм

Две задачи выполняются одновременно

Как можно одновременно выполнить две задачи? Очень просто  —  поручить их выполнение двум работникам (потокам). В этом суть параллелизма. Термин говорит сам за себя.

Можно предположить, что параллелизм равен многозадачности, но это не совсем так. Параллелизм более производителен в случаях, когда нужно выполнить большой процесс обработки, но если задача имеет узкое место для ввода-вывода, то это просто пустая трата времени. Что я имею в виду?

Возьмем простой пример. Вы поручили приготовить блюдо 3 поварам, каждый из которых должен работать отдельно с овощами, пастой и соусом. В итоге они потратили разное время: 2 минуты (на овощи), 1 минуту (на пасту), 3 минуты (на соус).

Как вы думаете, сколько времени ушло на приготовление блюда? 3 минуты!

Приготовление всего блюда было таким же медленным, как и самый медленный процесс. И то, что паста и овощи были готовы раньше, не имеет никакого значения. Более того, вы задействовали больше поваров, чем нужно: некоторых из них можно было эффективно использовать для приготовления других блюд.

Помимо этого, есть и другие проблемы, которые приходится решать при параллелизме (в отличие от многозадачности).

  • Состояние гонки (race condition). Гонка возникает, когда два процесса пытаются получить доступ к общей памяти. Это может создать проблемы. Представьте, что ваш банковский баланс равен $100, и вы одновременно проводите две транзакции, которые списывают $100. Что произойдет с банковским балансом  —  он станет 0 или -100? Эта ситуация должна быть правильно обработана в параллельных системах.
  • Передача сообщений (message passing). Этот механизм задействуется, когда два потока изолированы друг от друга. Допустим, у вас есть две задачи, A и B, выполняемые параллельно. Как вы передадите результат задачи A задаче B, ведь они изолированы друг от друга? Здесь как раз и необходим механизм передачи сообщений.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Suyash Singh: Understanding Concurrency and Parallelism: A Beginner’s Guide

Предыдущая статьяНавигация по ландшафту ИИ в 2024 году: тренды, прогнозы, возможности. Часть 2
Следующая статьяЗачем использовать HttpClientFactory вместо HttpClient в .NET