Третья ключевая концепция программирования -- это параллелизм в смысле прежде всего независимого и потенциально одновременного выполнения. Мы конструируем программу из независимых частей. Например, в императивном программировании программа состоит из последовательных инструкций, которые выполняются одна за другой и зависимы друг от друга (упорядочены по времени). В случае же, если инструкции будут независимы, мы можем исполнять их одновременно в любом порядке.
Когда две части кода никак друг от друга не зависит (не связываются), они называются/считаются параллельными. Когда же явно задан порядок исполнения двух частей кода, они называются последовательными. Параллельные части могут быть дополнены некоторым чётко определённым протоколом взаимодействия друг с другом, которое называется коммуникация.
Предыдущая серия:
Параллелизм в смысле одновременности (concurrency) как программистскую концепцию, не следует смешивать с параллелизмом в смысле parallelism -- аппаратной концепцией. Эти понятия ортогональны: независимые части кода могут выполняться "одновременно" на единственном процессоре (используя механизм разделения времени между процессами), и единственная программа может исполняться параллельно на мультипроцессорной системе (за счёт автоматического программно-аппаратного распараллеливания).
Реальный мир параллельный в смысле одновременности: он состоит из множества активностей, которые развиваются независимо. Мир программных систем также по сути параллельный, и в нём очень часто разрабатываются модели реального мира, поэтому параллельность в программировании так важна.
Следует различать несколько видов параллелизма (concurrency):
-- распределённая система: набор компьютеров, соединённых через сеть. Каждая независимая параллельная активность -- это компьютер. Это, в частности, базовая структура Интернета.
-- процессы в операционной системе (софт, который управляет компьютером). Каждая независимая параллельная активность -- это процесс. Процессы обычно используют независимые области памяти. Операционная система занимается связью прикладных программ, вычислительных ресурсов, процессов, памяти и компьютера. Например, обычной программе как правило выделяется один процесс, внутри которого она выполняется (активности внутри одного процесса).
-- нити в прикладном софте. Каждая независимая параллельная активность -- это нить, тред (thread). Нити выполняются независимо, однако используют общую область памяти. Например, закладки внутри браузера (активности внутри одной родительской программы, выполняющейся внутри одного процесса) обычно работают в отдельных нитях.
Фундаментальное отличие процессов от нитей в том, как они управляют ресурсами. Параллелизм на уровне процессов -- это нечто в духе соревнования: каждый процесс пытается захватить под себя все системные ресурсы. ОС старается играть роль судьи и обрабатывать запросы на ресурсы достаточно справедливо.
Параллелизм на уровне нитей -- это нечто в духе кооперации: нити в процессе разделяют ресурсы и действуют совместно, чтобы процесс выдал наилучший результат. Нити обычно принадлежат одному приложению и управляются им.
Существуют две популярные парадигмы параллелизма. Первая называется параллелизм с разделяемым состоянием: нити получают доступ к общим элементам данных через специальные контролирующие структуры, называемые мониторы, управляющие параллельным доступом.
Имеется отдельный способ параллельной работы с разделяемым состоянием -- через транзакции. Нити выполняют атомарные обновления общих структур данных, во время которых доступ других нитей к ним блокируется. Эта парадигма очень популярна и реализована во всех массовых языках, например Java и C шарп.
Вторая парадигма называется параллелизм с обменом сообщениями. Одновременно действующие агенты выполняются каждый в отдельной нити, и обмениваются друг с другом сообщениями. Такая схема поддерживается например в языке Erlang. При этом процесс обмена может быть как синхронным (агент, отправивший сообщение, находится в состоянии ожидания, пока целевой агент не получит это сообщение), и асинхронным (агент отправил сообщение и забыл о нём).
Концепция мониторов наиболее сложна для реализации. Транзакции и обмен сообщениями проще, но все они тоже весьма трудоёмки. Эти три подхода различаются своей выразительностью: им приходится как-то выражать недетерминизм программы (выполнение не определяется однозначно спецификацией), и именно поэтому в целом трудно рассуждать о правильности кода.
Параллельное программирование было бы гораздо проще, если бы недетерминизм контролировался таким образом, чтобы он не был "виден" программисту. На конкретных курсах мы изучим важные парадигмы и соответствующие технологии, которые реализуют эту идею -- сделать параллельное программирование намного проще.
продолжение следует
В следующей заметке подробнее рассмотрим именованное состояние.