Архитектуры многопоточных приложений
Многопроцессные приложения с автономными процессами
Это самый простой тип многопроцессных приложений. Для каждой пользовательской сессии или даже для каждого запроса создается свой процесс. Он обрабатывает запрос или несколько запросов и завершается.
Иногда такие процессы "взаимодействуют" при записи сообщений в лог-файл. Для разрешения возникающих при этом коллизий в Unix-системах предусмотрен флаг O_APPEND при открытии файла. При записи в файлы, открытые с этим флагом, указатель записи всегда перемещается на конец файла. Таким образом, записи в логе, выполняемые разными процессами, никогда несмешиваются. В более современныхUnix-системах для ведения логов предоставляется специальный сервис syslog(3C).
Преимущества:
- Простота разработки. Фактически, мы запускаем много копий однопоточного приложения и они работают независимо друг от друга. Можно не использовать никаких специфически многопоточных API и средств межпроцессного взаимодействия.
- Высокая надежность. Аварийное завершение любого из процессов никак не затрагивает остальные процессы.
- Хорошая переносимость. Приложение будет работать налюбой многозадачной ОС
- Высокая безопасность. Разные процессы приложения могут запускаться от имени разных пользователей. Таким образом можно реализовать принцип минимальных привилегий, когда каждый из процессов имеет лишь те права, которые необходимы ему для работы. Даже если в каком-то из процессов будет обнаружена ошибка, допускающая удаленное исполнение кода, взломщик сможет получить лишь уровень доступа, с которым исполнялся этот процесс.
Недостатки:
- Далеко не все прикладные задачи можно предоставлять таким образом. Например, эта архитектура годится для сервера, занимающегося раздачей статических HTMLстраниц, но совсем непригодна для сервера баз данных и многих серверов приложений.
- Создание и уничтожение процессов – дорогая операция, поэтому для многих задач такая архитектура неоптимальна.
В Unix-системах предпринимается целый комплекс мер для того, чтобы сделать создание процесса и запуск новой программы в процессе как можно более дешевыми операциями. Однако нужно понимать, что создание нити в рамках существующего процесса всегда будет дешевле, чем создание нового процесса.
Примеры: apache 1.x (сервер HTTP)
Многопроцессные приложения, взаимодействующие через сокеты, трубы и очереди сообщений System V IPC
Перечисленные средства IPC (Interprocess communication) относятся к так называемым средствам гармонического межпроцессного взаимодействия. Онипозволяют организовать взаимодействие процессов и потоков без использования разделяемой памяти. Теоретики программирования очень любят эту архитектуру, потому что она практически исключает многие варианты ошибок соревнования.
Преимущества:
- Относительная простота разработки.
- Высокая надежность. Аварийное завершение одного из процессов приводит к закрытию трубы или сокета, а в случае очередей сообщений – к тому, что сообщения перестают поступать в очередь или извлекаться из нее. Остальные процессы приложения легко могут обнаружить эту ошибку и восстановиться после нее, возможно (но не обязательно) просто перезапустив отказавший процесс.
- Многие такие приложения (особенно основанные на использовании сокетов) легко переделываются для исполненияв распределенной среде, когда разные компоненты приложения исполняются на разных машинах.
- Хорошая переносимость. Приложение будет работать на большинстве многозадачных ОС, в том числе на старых Unix-системах.
- Высокая безопасность. Разные процессы приложения могут запускаться от имени разных пользователей. Таким образом можно реализовать принцип минимальных привилегий, когда каждый из процессов имеет лишь те права, которые необходимы ему для работы.
Даже если в каком-то из процессов будет обнаружена ошибка, допускающая удаленное исполнение кода, взломщик сможет получить лишь уровень доступа, с которым исполнялся этот процесс.
Недостатки:
- Не для всех прикладных задач такую архитектуру легко разработать и реализовать.
- Все перечисленные типы средств IPC предполагают последовательную передачу данных. Если необходим произвольный доступ к разделяемым данным, такая архитектура неудобна.
- Передача данных через трубу, сокет и очередь сообщений требует исполнения системных вызовов и двойного копирования данных – сначала из адресного пространства исходного процесса в адресное пространство ядра, затем из адресного пространства ядра в память целевого процесса. Это дорогие операции. При передаче больших объемов данных это может превратиться в серьезную проблему.
- В большинстве систем действуют ограничения на общее количество труб, сокетов и средств IPC. Так, в Solaris по умолчанию допускается не более 1024 открытых труб, сокетов и файлов на процесс (это обусловлено ограничениями системного вызова select). Архитектурное ограничение Solaris – 65536 труб, сокетов и файлов на процесс.
Ограничение на общее количество сокетов TCP/IP – не более 65536 на сетевой интерфейс (обусловлено форматом заголовков TCP). Очереди сообщений System V IPC размещаются вадресном пространствеядра, поэтому действуют жесткиеограничения на количество очередей в системе и на объем и количество одновременно находящихся в очередях сообщений.
- Создание и уничтожение процесса, а также переключение между процессами – дорогие операции. Не во всех случаях такая архитектура оптимальна.
Многопроцессные приложения, взаимодействующие через разделяемую память
В качестве разделяемой памяти может использоваться разделяемая память System V IPC и отображение файлов на память. Для синхронизации доступа можно использовать семафоры System V IPC, мутексы и семафоры POSIX, при отображении файлов на память – захват участков файла.
Преимущества:
- Эффективный произвольный доступ к разделяемым данным. Такая архитектура пригодна для реализации серверов баз данных.
- Высокая переносимость. Может быть перенесено налюбую операционную систему, поддерживающую или эмулирующую System V IPC.
- Относительно высокая безопасность. Разные процессыприложениямогут запускаться от имени разных пользователей. Таким образом можно реализовать принцип минимальных привилегий, когда каждый из процессов имеет лишь те права, которые необходимы ему для работы. Однако разделение уровней доступа не такое жесткое, как в ранее рассмотренных архитектурах.
Недостатки:
- Относительная сложность разработки. Ошибки при синхронизации доступа – так называемые ошибки соревнования – очень сложно обнаруживать при тестировании.
Это может привести к повышению общей стоимости разработки в 3–5 раз по сравнению с однопоточными или более простыми многозадачными архитектурами.
- Низкая надежность. Аварийное завершение любого из процессов приложения может оставить (и часто оставляет) разделяемую память в несогласованном состоянии.
Это часто приводит к аварийному завершению остальных задач приложения. Некоторые приложения, например Lotus Domino, специально убивают всесерверные процессы при аварийном завершении любого из них.
- Создание и уничтожение процесса и переключение между ними – дорогие операции.
Поэтому данная архитектура оптимальна не для всех приложений.
- При определенных обстоятельствах, использование разделяемой памяти может приводить к эскалации привилегий. Если в одном из процессов будет найдена ошибка, приводящая к удаленному исполнению кода, с высокой вероятностью взломщик сможет ее использовать для удаленного исполнения кода в других процессах приложения.
То есть, в худшем случае, взломщик может получить уровень доступа, соответствующий наивысшему из уровней доступа процессов приложения.
- Приложения, использующие разделяемую память, должны исполняться на одном физическом компьютереили, во всяком случае, на машинах, имеющих разделяемое ОЗУ. В действительности, это ограничение можно обойти, например используя отображенные на память разделяемые файлы, но это приводит к значительным накладным расходам
Фактически, данная архитектура сочетает недостатки многопроцессных и собственно многопоточных приложений. Тем не менее, ряд популярных приложений, разработанных в 80е и начале 90х, до того, как в Unix были стандартизованы многопоточные API, используют эту архитектуру. Это многие серверы баз данных, как коммерческие (Oracle, DB2, Lotus Domino), такисвободно распространяемые,современные версии Sendmail инекоторые другие почтовые серверы.
Собственно многопоточные приложения
Потоки или нити приложения исполняются в пределах одного процесса. Все адресное пространство процесса разделяется между потоками. На первый взгляд кажется, что это позволяет организовать взаимодействие между потоками вообще без каких-либо специальных API. В действительности, это не так – если несколько потоков работает с разделяемой структурой данных или системным ресурсом, и хотя бы один из потоков модифицирует эту структуру, то в некоторые моменты времени данные будут несогласованными.
Поэтому потоки должны использовать специальные средства для организации взаимодействия. Наиболее важные средства – это примитивы взаимоисключения (мутексы и блокировки чтения-записи). Используя эти примитивы, программист может добиться того, чтобы ни один поток не обращался к разделяемым ресурсам, пока они находятся в несогласованном состоянии (это и называется взаимоисключением). POSIX thread library предоставляет и другие примитивы (например, условные переменные), позволяющие организовать более сложные, чем взаимоисключение, схемы взаимодействия между нитями.
Преимущества:
- Высокая производительность. На большинстве Unix-систем, создание нити требует в десятки раз меньше процессорного времени, чем создание процесса.
- Эффективный произвольный доступ к разделяемым данным. В частности, такая архитектура пригодна для создания серверов баз данных.
- Высокая переносимость илегкость переноса ПОиз-под других ОС, реализующих многопоточность.
Недостатки:
- Высокая вероятность опасных ошибок. Все данные процесса разделяются между всеми нитями. Иными словами, любая структура данных может оказаться разделяемой, даже если разработчик программы не планировал этого (для сравнения, в многопроцессных приложениях, использующих разделяемую память System V IPC, разделяются только те структуры, которые размещены в сегменте разделяемой памяти. Обычные переменные и размещаемые обычным образом динамические структуры данных свои укаждого изпроцессов). Ошибки придоступекразделяемым данным – ошибки соревнования – очень сложно обнаруживать при тестировании.
- Высокая стоимость разработки и отладки приложений, обусловленная п. 1.
- Низкая надежность. Разрушение структур данных, например в результате переполнения буфера или ошибок работы с указателями, затрагивает все нити процесса и обычно приводит к аварийному завершению всего процесса. Другие фатальные ошибки, например, деление на ноль в одной из нитей, также обычно приводят к аварийной остановке всех нитей процесса.
- Низкая безопасность. Все нити приложения исполняются в одном процессе, то есть от имени одного и того же пользователя и с одними и теми же правами доступа. Невозможно реализовать принцип минимума необходимых привилегий, процесс должен исполняться от имени пользователя, который может исполнять все операции, необходимые всем нитям приложения.
- Создание нити – все-таки довольно дорогая операция. Для каждой нити в обязательном порядке выделяется свой стек, который по умолчанию занимает 1 мегабайт ОЗУ на 32битных архитектурах и 2 мегабайта на 64-битных архитектурах, и некоторые другие ресурсы. Поэтому данная архитектура оптимальна не для всех приложений.
- Невозможность исполнять приложение на многомашинном вычислительном комплексе. Упоминавшиеся в предыдущем разделе приемы, такие, как отображение на память разделяемых файлов, для многопоточной программы не применимы.
В целом можно сказать, что многопоточные приложения имеют почти те же преимущества и недостатки, что и многопроцессные приложения, использующие разделяемую память.
Однако стоимость исполнения многопоточного приложения ниже, а разработка такого приложения в некоторых отношениях проще, чем приложения, основанного наразделяемой памяти. Поэтому в последние годы многопоточные приложения становятся все более и более популярны.