UNIX: взаимодействие процессов
Шрифт:
1. Считать порядковый номер из файла.
2. Использовать этот номер.
3. Увеличить его на единицу и записать обратно в файл.
Проблема в том, что пока один процесс выполняет эти три действия, другой процесс может параллельно делать то же самое. В итоге возникнет полный беспорядок с номерами, как мы увидим в следующих примерах.
ПРИМЕЧАНИЕ
Описанная выше проблема называется проблемой взаимных исключений. Она может быть решена с использованием взаимных исключений из главы 7 или блокировок чтения-записи из главы 8. Различие состоит в том, что здесь мы предполагаем неродственность процессов, что усложняет использование предложенных выше методов. Мы могли бы использовать разделяемую память (подробно об этом говорится в четвертой части книги), поместив в нее переменную синхронизации одного из этих типов, но для неродственных процессов проще воспользоваться блокировкой fcntl. Другим фактором в данном случае стало то, что проблема со спулерами печати возникла задолго до появления взаимных исключений, условных переменных и блокировок чтения-записи. Блокировка записей была добавлена в Unix в начале 80-х, до того как появились концепции разделяемой памяти и программных потоков.
Таким образом, процессу нужно заблокировать файл, чтобы никакой другой процесс не мог получить к нему доступ, пока первый выполняет свои три действия. В листинге 9.2 приведен текст простой программы, выполняющей соответствующие действия. Функции my_lock и my_unlock обеспечивают блокирование и разблокирование файла в соответствующие моменты. Мы приведем несколько возможных вариантов реализации этих функций.
20 Каждый раз при прохождении цикла мы выводим имя программы (argv[0]) перед порядковым номером, поскольку эта функция main будет использоваться с различными версиями функций блокировки и нам бы хотелось видеть, какая версия программы выводит данную последовательность порядковых номеров.
ПРИМЕЧАНИЕ
Вывод идентификатора процесса требует преобразования переменной типа pid_t к типу long и последующего использования строки формата %ld. Проблема тут в том, что идентификатор процесса принадлежит к одному из целых типов, но мы не знаем, к какому именно, поэтому предполагается наиболее вместительный — long. Если бы мы предположили, что идентификатор имеет тип int и использовали бы строку %d, a pid_t на самом деле являлся бы типом long, код мог бы работать неправильно.
Посмотрим, что будет, если не использовать блокировку. В листинге 9.1 [1] приведены версии функций my_lock и my_unlock, которые вообще ничего не делают.
1
Все исходные тексты, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com/download.
Если начальное значение порядкового номера в файле было 1 и был запущен только один экземпляр программы, мы увидим следующий результат:
ПРИМЕЧАНИЕ
Обратите внимание, что функция main хранится в файле lockmain.c, но мы компилируем и компонуем эту программу с функциями, не осуществляющими никакой блокировки (листинг 9.1), поэтому мы называем ее locknone. Ниже будут использоваться другие версии функций my_lock и my_unlock, и исполняемый файл будет называться по-другому в соответствии с используемым методом блокировки.