MyWackoSite: КурсОперационныеСистемы/ПрактикумPosixThreads/PthreadLectures/Lecture1 ...

Home Page | Каталог | Изменения | НовыеКомментарии | Пользователи | Регистрация | Вход:  Пароль:  
Это старая версия КурсОперационныеСистемы/ПрактикумPosixThreads/PthreadLectures/Lecture1 за 2006-09-08 16:59:56..
Задача этого курса – обучить вас разработке многопоточных приложений для Sun Solaris 10. Значительная часть полученных знаний может быть использована для разработки многопоточных приложений для других систем семейства Unix, поддерживающих POSIX threads API. Это такие системы, как Linux (начиная с версии 2.4), Free BSD?, SCO Unixware, IBM AIX и др.

1. Что такое потоки


В Unix-системах для исполнения программ используются объекты, называемые процессами. Процессу выделяется собственное виртуальное адресное пространство и некоторые другие ресурсы. Для каждого процесса создается иллюзия последовательного исполнения. Программа исполняется в рамках процесса так же, как она исполнялась бы в однозадачной ОС.

Процесс взаимодействует с ядром ОС при помощи так называемых системных вызовов. При исполнении системного вызова, процесс исполняет специальную команду (у современных версий x86 эта команда называется SYSCALL, у SPARC – TA 0x8 для 32-битных программ, TA 0x40 для 64-битных программ), которая переключает адресное пространство и передает управление ядру. Процессы в традиционных Unix-системах могут взаимодействовать друг с другом только при помощи системных вызовов – чтения и записи в трубы и разделяемые файлы, а в более современных системах – при помощи System V IPC.

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

Однако ряд задач – примеры которых мы рассмотрим далее в этом разделе – требует реализации в виде нескольких параллельно (или, на однопроцессорной машине, квазипараллельно) исполняющихся процессов. Традиционные Unix-системы предполагали решать такие задачи при помощи нескольких взаимодействующих процессов. Среди системных вызовов, поддерживаемых всеми современными Unix-системами можно выделить несколько групп, специально предназначенных для такого взаимодействия – это вызовы для работы с трубами (pipe), сетевые средства (которые можно применять и для взаимодействия между процессами на одной машине) и System V IPC. К сожалению, далеко не для всех задач эти средства оптимальны, а для некоторых и вовсе неадекватны.

Кроме того, нередко возникала потребность в переносе приложений, разработанных для ОС, допускавших несколько нитей исполнения в пределах одной задачи или процесса, под Unix.

Поэтому в современных Unix-системах было введено понятие нитей (thread), которые соответствуют единицам планирования в рамках одного процесса. Нити разделяют общее адресное пространство, но планируются независимо. Иллюзия последовательного исполнения создается для нити, а не для процесса в целом.

В начале 90х в различных Unix-системах использовались разные подходы к реализации многопоточности и несовместимые API для управления потоками. В 1995 году был принят стандарт IEEE POSIX 1003.1c-1995 (также известен как ISO/IEC 9945-1:1996). Большинство современных Unix-систем, а также некоторые не-Unix системы (например, Open VMS? и IBM zOS) реализуют этот стандарт.
При разработке новых приложений рекомендуется пользоваться стандартным API. Это облегчит вам перенос приложений под другие ОС и под новые версии Solaris.

2. Зачем нужны многопоточные программы


  1. Улучшение времени реакции интерактивных программ. Примеры: фоновое скачивание страницы в браузере, фоновая проверка орфографии, фоновое переразбиение текста на страницы в WYSIWYG текстовых процессорах.
  2. Улучшение времени реакции серверных приложений. Возможность обрабатывать несколько запросов одновременно.
  3. Использование дополнительных ресурсов на многопроцессорных и гипертрединговых компьютерах.
  4. Задачи реального времени

2.1 Улучшение времени реакции серверных приложений


Главные характеристики большинства серверов приложений – это среднее время исполнения запроса и количество запросов, обрабатываемых в единицу времени. Это две разные характеристики.

Среднее время исполнения запроса – это основная характеристика производительности сервера с точки зрения отдельного пользователя. Увеличение этого времени ведет к снижению производительности пользователя, ему приходится тратить больше времени на исполнение тех же самых задач. Кроме того, слишком большое время реакции системы попросту раздражает пользователей.

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

Рассмотрим процесс исполнения одиночного запроса типичным серверным приложением (см. рис. 1). Он состоит из пяти этапов: приема запроса (выполняется сетевым интерфейсом), анализа запроса (выполняется центральным процессором), считывания данных с диска (выполняется дисковым контроллером), создания ответа (выполняется центральным процессором) и передачи ответа. Разумеется, времена исполнения этих этапов могут различаться в зависимости от типа сервиса, предоставляемого сервером, и от самих запросов. Для сервера http, раздающего статические HTML страницы, время анализа запроса и форматирования ответа будет очень малым по сравнению с временами чтения с диска и передачи данных по сети. Напротив, у сервера приложений основная нагрузка ляжет на центральный процессор. Серверу баз данных может потребоваться несколько обращений к диску для того, чтобы найти все относящиеся к запросу данные, и т.д.. Тем не менее, давайте рассмотрим условный сервер, обрабатывающий однотипные запросы, у которых структура обработки запроса именно такова, как на рис. 1, а времена исполнения всех этапов одинаковы (последнее требование нужно лишь для того, чтобы мне было проще рисовать картинку).



Рис. 1 Исполнение одиночного запроса серверным приложением.

Рассмотрим теперь исполнение потока запросов. Однопоточный сервер должен был бы исполнять запросы строго последовательно, поэтому максимальное количество запросов, исполняемых в секунду, было бы равно 1/t, где t – время исполнения одиночного запроса. При этом среднее время исполнения запроса не будет равно t, а будет расти в зависимости от вероятности перекрытия запросов во времени. Теоретико-вероятностные расчеты и практика показывают, что когда поток запросов приближается к 1/t в секунду, эта вероятность становится весьма значительной, так что время исполнения одиночного запроса может увеличиться во много раз.

Однако к многопоточному серверу эти расчеты неприменимы. Рассмотрим, как многопоточный сервер мог бы обрабатывать поток запросов (см. рис. 2).



Рис. 2 Исполнение потока запросов многопоточным сервером.

Видно, что сервер совмещает этапы исполнения перекрывающихся во времени запросов. Если относительные времена исполнения этапов запросов таковы, как на рис. 1 и 2, избежать взаимодействия запросов не удается, так что среднее время исполнения запроса может вырасти по сравнению с t. Но количество запросов, исполняемых в секунду, оказывается значительно больше, чем 1/t.
Более того, видно, что мы могли бы повысить производительность системы, установив второй процессор и второй сетевой интерфейс.

Однако переделка однопоточного серверного приложения в многопоточное требует специальной поддержки со стороны операционной системы и значительной переработки кода приложения. Все современные ОС общего назначения – Sun Solaris, другие системы семейства Unix, IBM zOS, Win 32? и Win 64? – обеспечивают соответствующую поддерку, но далеко не все разработчики приложений умеют этой поддержкой пользоваться.

3. Архитектуры многопоточных приложений


  1. Многопроцессные приложения с автономными процессами
  2. Многопроцессные приложения, взаимодействующие через трубы, сокеты и очереди System V IPC
  3. Многопроцессные приложения, взаимодействующие через разделяемую память
  4. Собственно многопоточные приложения
  5. Событийно-ориентированные приложения
  6. Гибридные архитектуры

3.1 Многопроцессные приложения с автономными процессами


Это самый простой тип многопроцессных приложений. Для каждой пользовательской сессии или даже для каждого запроса создается свой процесс. Он обрабатывает запрос или несколько запросов и завершается.

Преимущества:

  1. Простота разработки. Фактически, мы запускаем много копий однопоточного сервера и они работают независимо друг от друга. Можно не использовать никаких специфически многопоточных API и средств межпроцессного взаимодействия.
  2. Высокая надежность. Аварийное завершение любого из серверных процессов никак не затрагивает остальные серверные процессы.
  3. Хорошая переносимость. Приложение будет работать на любой многозадачной ОС 
  4. Высокая безопасность. Разные процессы приложения могут запускаться от имени разных пользователей. Таким образом можно реализовать принцип минимальных привилегий, когда каждый из процессов имеет лишь те права, которые необходимы ему для работы. Даже если в каком-то из процессов будет обнаружена ошибка, допускающая удаленное исполнение кода, взломщик сможет получить лишь уровень доступа, с которым исполнялся этот процесс.

Недостатки:

  1. Далеко не все сервисы можно предоставлять таким образом. Например, эта архитектура годится для раздачи статических HTML-страниц, но совсем непригодна для сервера баз данных и многих серверов приложений.
  2. Создание и уничтожение процессов – дорогая операция, поэтому для многих задач такая архитектура неоптимальна.

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

Примеры: apache 1.x (сервер HTTP)

3.2 Многопроцессные приложения, взаимодействующие через сокеты, трубы и очереди сообщений System V IPC


Перечисленные средства IPC (Interprocess communication) относятся к так называемым средстам гармонического межпроцессного взаимодействия. Они позволяют организовать взаимодействие процессов и потоков без использования разделяемой памяти. Как мы увидим дальше, это дает значительные преимущества.

Преимущества:

  1. Относительная простота разработки. Теоретики программирования очень любят эту архитектуру, потому что она позволяет избежать специфических ошибок программирования (так называемых ошибок соревнования), характерных для многопоточных программ, которые очень сложно обнаруживать при тестировании. Большая часть нашего курса посвящена тому, как избежать ошибок соревнования при разработке многопоточных программ.
  2. Высокая надежность. Аварийное завершение одного из серверных процессов приводит к закрытию трубы или сокета, а в случае очередей сообщений – к тому, что сообщения перестают поступать в очередь или извлекаться из нее. Остальные процессы приложения легко могут обнаружить эту ошибку и восстановиться после нее, возможно (но не обязательно) просто перезапустив отказавший процесс.
  3. Многие такие приложения (особенно основанные на использовании сокетов) легко переделываются для исполнения в распределенной среде, когда разные компоненты приложения исполняются на разных машинах.
  4. Хорошая переносимость. Приложение будет работать на большинстве многозадачных ОС, в том числе на старых Unix-системах.
  5. Высокая безопасность. Разные процессы приложения могут запускаться от имени разных пользователей. Таким образом можно реализовать принцип минимальных привилегий, когда каждый из процессов имеет лишь те права, которые необходимы ему для работы.
Даже если в каком-то из процессов будет обнаружена ошибка, допускающая удаленное исполнение кода, взломщик сможет получить лишь уровень доступа, с которым исполнялся этот процесс.

Недостатки:

  1. Не для всех типов сервисов такую архитектуру легко разработать и реализовать.
  2. Все перечисленные типы средств IPC предполагают последовательную передачу данных. Если необходим произвольный доступ к разделяемым данным, такая архитектура неудобна.
  3. Передача данных через трубу, сокет и очередь сообщений требует исполнения системных вызовов и двойного копирования данных – сначала из адресного пространства исходного процесса в адресное пространство ядра, затем из адресного пространства ядра в память целевого процесса. Это дорогие операции. При передаче больших объемов данных это может превратиться в серьезную проблему.
  4. В большинстве систем действуют ограничения на общее количество труб, сокетов и средств IPC. Так, в Solaris по умолчанию допускается не более 1024 открытых труб, сокетов и файлов на процесс (это обусловлено ограничениями системного вызова select). Архитектурное ограничение Solaris – 65536 труб, сокетов и файлов на процесс. Ограничение на общее количество сокетов TCP/IP – не более 65536 на сетевой интерфейс (обусловлено форматом заголовков TCP). Очереди сообщений System V IPC размещаются в адресном пространстве ядра, поэтому действуют жесткие ограничения на количество очередей в системе и на объем и количество одновременно находящихся в очередях сообщений.
  5. Создание и уничтожение процесса, а также переключение между процессами – дорогие операции. Не во всех случаях такая архитектура оптимальна.

3.3 Многопроцессные приложения, взаимодействующие через разделяемую память


В качестве разделяемой памяти может использоваться разделяемая память System V IPC и отображение файлов на память. Для синхронизации доступа можно использовать семафоры System V IPC, мутексы и семафоры POSIX, при отображении файлов на память – захват участков файла.

Преимущества:

  1. Эффективный произвольный доступ к разделяемым данным. Такая архитектура пригодна для реализации серверов баз данных.
  2. Высокая переносимость. Может быть перенесено на любую операционную систему, поддерживающую или эмулирующую System V IPC.
  3. Относительно высокая безопасность. Разные процессы приложения могут запускаться от имени разных пользователей. Таким образом можно реализовать принцип минимальных привилегий, когда каждый из процессов имеет лишь те права, которые необходимы ему для работы. Однако разделение уровней доступа не такое жесткое, как в ранее рассмотренных архитектурах.

Недостатки:

  1. Относительная сложность разработки. Ошибки при синхронизации доступа – так называемые ошибки соревнования – очень сложно обнаруживать при тестировании. Это может привести к повышению общей стоимости разработки в 3–5 раз по сравнению с однопоточными или более простыми многозадачными архитектурами.
  2. Низкая надежность. Аварийное завершение любого из процессов приложения может оставить (и часто оставляет) разделяемую память в несогласованном состояниии. Это часто приводит к аварийному завершению остальных задач приложения. Некоторые приложения, например Lotus Domino, специально убивают все серверные процессы при аварийном завершении любого из них.
  3. Создание и уничтожение процесса и переключение между ними – дорогие операции. Поэтому данная архитектура оптимальна не для всех приложений.
  4. При определенных обстоятельствах, использование разделяемой памяти может приводить к эскалации привилегий. Если в одном из процессов будет найдена ошибка, приводящая к удаленному исполнению кода, с высокой вероятностью взломщик сможет ее использовать для удаленного исполнения кода в других процессах приложения. То есть, в худшем случае, взломщик может получить уровень доступа, соответствующий наивысшему из уровней доступа процессов приложения.

Фактически, данная архитектура сочетает недостатки многопроцессных и собственно многопоточных приложений. Тем не менее, ряд популярных приложений, разработанных в 80е и начале начале 90х, до того, как в Unix были стандартизованы многопоточные API, используют эту архитектуру. Это многие серверы баз данных, как коммерческие (Oracle, DB2, Lotus Domino), так и свободно распространяемые, современные версии Sendmail и некоторые другие почтовые серверы.

3.4 Собственно многопоточные приложения


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

Поэтому потоки должны использовать специальные средства для организации взаимодействия. Наиболее важные средства – это примитивы взаимоисключения (мутексы и блокировки чтения-записи). Используя эти примитивы, программист может добиться того, чтобы ни один поток не обращался к разделяемым ресурсам, пока они находятся в несогласованном состоянии (это и называется взаимоисключением). POSIX thread library предоставляет и другие примитивы (например, условные переменные), позволяющие организовать более сложные, чем взаимоисключение, схемы взаимодействия между нитями.

Преимущества:

  1. Высокая производительность. На большинстве Unix-систем, создание процесса требует в десятки раз больше процессорного времени, чем создание нити.
  2. Эффективный произвольный доступ к разделяемым данным. В частности, такая архитектура пригодна для создания серверов баз данных.
  3. Высокая переносимость и легкость переноса ПО из-под других ОС, реализующих многопоточность.

Недостатки:

  1. Высокая вероятность опасных ошибок. Все данные процесса разделяются между всеми нитями. Иными словами, любая структура данных может оказаться разделяемой, даже если разработчик программы не планировал этого (для сравнения, в многопроцессных приложениях, использующих разделяемую память System V IPC, разделяются только те структуры, которые размещены в сегменте разделяемой памяти. Обычные переменные и размещаемые обычным образом динамические структуры данных свои у каждого из процессов). Ошибки при доступе к разделяемым данным – ошибки соревнования – очень сложно обнаруживать при тестировании.
  2. Высокая стоимость разработки и отладки приложений, обусловленная п. 1.
  3. Низкая надежность. Разрушение структур данных, например в результате переполнения буфера или ошибок работы с указателями, затрагивает все нити процесса и обычно приводит к аварийному завершению всего процесса. Другие фатальные ошибки, например, деление на ноль в одной из нитей, также обычно приводят к аварийной остановке всех нитей процесса.
  4. Низкая безопасность. Все нити приложения исполняются в одном процессе, то есть от имени одного и того же пользователя и с одними и теми же правами доступа. Невозможно реализовать принцип минимума необходимых привилегий, процесс должен исполняться от имени пользователя, который может исполнять все операции, необходимые всем нитям приложения.
  5. Создание нити – все-таки довольно дорогая операция. Для каждой нити в обязательном порядке выделяется свой стек, который по умолчанию занимает 1 мегабайт ОЗУ на 32-битных архитектурах и 2 мегабайта на 64-битных архитектурах, и некоторые другие ресурсы. Поэтому данная архитектура оптимальна не для всех приложений.

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

3.5 Событийно-ориентированные архитектуры


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

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

Главная сложность при разработке событийно-ориентированного приложения – это гарантировать, что все обработчики событий будут завершаться достаточно быстро. Как правило это означает, что обработчики не должны вызывать блокирующихся системных вызовов. Если удастся соблюсти это требование, то событийно-ориентированная архитектура позволяет получить многие преимущества многозадачности и многопоточности без использования собственно многопоточности. В этом смысле можно даже сказать, что событийно-ориентированная архитектура – это альтернатива многозадачным и многопоточным приложениям.

Событийно-ориентированные архитектуры применяются во множестве различных случаев. Они используются для реализации интерактивных приложений с графическим пользовательским интерфейсом. Большинство инструментальных средств для разработки многооконных приложений – Qt, GTK, AWT – содержат встроенный менеджер событий. В Win 32? менеджер событий графического интерфейса встроен в ядро операционной системы. Также событийно-ориентированная архитектура часто используется при разработке серверных приложений. В этом случае, менеджер событий чаще всего строится на основе системных вызовов select или poll.
Ядра большинства современных операционных систем, таких, как Windows, Linux и BSD Unix, также имеют событийно-ориентированную архитектуру.

Преимущества

  1. Высокая производительность. Грамотно разработанное событийно-ориентированное приложение может одновременно обрабатывать множество событий в рамках одного потока и одного процесса. Некоторые событийно-ориентированные приложения обрабатывают сотни и тысячи сетевых соединений в одном потоке.

Недостатки:

  1. Не для всех приложений эта архитектура подходит. Так, если обработка события требует длительных вычислений или ее невозможно реализовать без использования блокирующихся системных вызовов, это может затормозить обработку других событий.
  2. Разработка событийно-ориентированного приложения требует высокой квалификации разработчиков. На практике это может привести, и обычно приводит, к высокой стоимости разработки.
  3. Код, рассчитанный на другую архитектуру (например, использующий блокирующиеся системные вызовы), невозможно переиспользовать в событийно-ориентированном приложении. Напротив, многие из приемов кодирования, необходимых в событийно-ориентированных приложениях, бесполезны и даже вредны в других архитектурах.
  4. Низкая надежность. Фатальная ошибка при обработке любого события приводит к аварийному завершению всего процесса.
  5. Низкая безопасность. Поскольку все события обрабатываются одним процессом, то все обработчики работают от имени одного и того же пользователя. Невозможно реализовать принцип минимально необходимых привилегий.

3.6 Гибридные архитектуры


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

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

В рамках этого курса мы рассмотрим гибридную архитектуру, известную под названием «рабочие нити» (worker threads), использующую сочетание событийно-ориентированной и многопоточной архитектур. Такую архитектуру используют многие серверные приложения, в том числе Apache 2.0.
 
Файлов нет. [Показать файлы/форму]
Комментариев нет. [Показать комментарии/форму]