Выбрать главу

3. Клиент открывает канал для чтения, читает первый ответ и закрывает свой канал.

4. Далее клиент посылает новую команду и открывает клиентский канал для чтения.

5. Сервер возобновляет работу, закрывая свой конец клиентского канала.

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

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

Резюме, касающееся приложения

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

Резюме 

В этой главе вы рассмотрели передачу данных между процессами с помощью каналов. Сначала вы познакомились с неименованными каналами, которые создаются вызовом popen или pipe, и посмотрели, как, применяя канал и вызов dup, можно передать данные из одной программы в стандартный ввод другой. Далее вы перешли к именованным каналам и узнали, как можно передавать данные между несвязанными программами. В заключение вы реализовали простой пример клиент- серверного приложения, используя каналы FIFO для обеспечения не только синхронизации процессов, но и организации двунаправленного потока данных. 

Глава 14

Семафоры, совместно используемая память и очереди сообщений

В этой главе мы обсудим набор средств, обеспечивающих взаимодействие процессов и первоначально введенных в версии ОС UNIX AT&T System V.2. Поскольку все эти средства появились в одном выпуске системы и обладают одинаковым программным интерфейсом, их часто называют средствами IPC (Inter-Process Communication, взаимодействие между процессами) или более полно System V IPC. Как вы уже видели, это далеко не единственный способ установления связи между процессами, но термин "System V IPC" обычно применяется для обозначения именно этих конкретных средств.

В данной главе мы рассмотрим следующие темы:

□ семафоры для управления доступом к ресурсам;

□ совместно используемая память для эффективного использования общих данных разными программами;

□ обмен сообщениями как легкий способ передачи данных между программами.

Семафоры

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

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

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

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

Примечание

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

Написать программный код общего назначения, который гарантирует одной программе монопольный доступ к конкретному ресурсу, на удивление сложно, несмотря на то, что существует решение, известное как алгоритм Деккера (Dekker's Algorithm). К сожалению, этот алгоритм полагается на состояние активного ожидания или спин-блокировки, в котором процесс выполняется непрерывно, ожидая изменения адреса памяти. В многозадачной среде, какой является ОС Linux, это нежелательные расходы ресурсов ЦПУ. Ситуация существенно облегчается, когда для обеспечения монопольного доступа есть аппаратная поддержка, обычно в виде специальных команд ЦПУ. Примером аппаратной поддержки могла бы быть команда обращения к ресурсу и приращения регистра атомарным образом, так чтобы никакая другая команда (даже прерывание) не могла появиться между операциями чтения/инкремента/записи.

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

Важный шаг вперед в сфере параллельного программирования был сделан, когда голландский специалист в области компьютерных наук Эдсгер Дейкстра (Edsger Dijkstra) предложил идею семафоров. Как уже кратко упоминалось в главе 12, семафор — это специальная переменная, которая принимает только целые положительные значения и с помощью которой программы могут действовать только атомарно. В этой главе мы расширим данное ранее упрощенное определение. Будет более подробно рассказано, как действуют семафоры и как для взаимодействия отдельных процессов применяются функции общего назначения вместо особого случая многопоточных программ, которые рассматривались в главе 12.

Определяя более строго, семафор — это специальная переменная, для которой разрешены только две операции, формально именуемые ожиданием или приостановкой (wait) и оповещением (signal). Поскольку в программировании Linux у приостановки и оповещения уже есть специальные значения, мы будем применять оригинальное обозначение:

□ P(переменная-семафор) для приостановки (wait);

□ V(переменная-семафор) для оповещения (signal).

Эти буквы взяты из голландских слов для приостановки (passeren — проходить, пропускать как в случае контрольной точки перед критической секцией) и для оповещения (vrijgeven — предоставлять или освобождать, как в случае отказа от контроля критической секции). Вы можете встретить термины "вверх" (up) и "вниз" (down), применяемые в отношении семафоров по аналогии с использованием сигнальных флажков.