Уничтожение анонимного семафора должно происходить до освобождения занимаемой им памяти. Например, если семафор является автоматически выделяемой переменной, то должен быть уничтожен до возвращения функции, в которой был создан. При нахождении семафора на участке разделяемой памяти POSIX его нужно удалить после того, как он перестает использоваться каким-либо процессом, но перед удалением объекта разделяемой памяти с помощью функции shm_unlink().
Часть систем позволяют пропустить вызов sem_destroy() без каких-либо проблем. Но существуют реализации, в которых это может привести к утечке ресурсов. Чтобы исключить такую возможность, портируемые приложения должны вызывать sem_destroy().
И POSIX-семафоры, и Pthreads-мьютексы могут использоваться для синхронизации потоков в рамках одного и того же процесса. Они мало чем отличаются с точки зрения производительности, однако мьютексы являются более предпочтительными, поскольку поддерживают атрибут, указывающий на владельца, что располагает к лучшему структурированию кода (только поток, закрывший мьютекс, может его открыть). Для сравнения, семафор можно инкрементировать из потока, который его не инкрементировал. Такая гибкость может стать причиной неудачной архитектуры приложения (именно поэтому семафоры иногда называют аналогом инструкции goto в параллельном программировании).
Существует один сценарий, в котором мьютексы не подходят для использования в многопоточном приложении и могут быть заменены семафорами. Функция sem_post() является безопасной для вызова в асинхронных сигналах (см. табл. 21.1), поэтому ее можно задействовать внутри обработчика сигнала для синхронизации с другим потоком. Мьютексы этого не позволяют, так как соответствующие функции библиотеки Pthreads небезопасны с точки зрения асинхронных сигналов. Однако данное преимущество семафоров редко оказывается востребованным, потому что асинхронные сигналы рекомендуется принимать в вызовах наподобие sigwaitinfo(), а не в самих обработчиках (см. подраздел 33.2.4).
В стандарте SUSv3 описано два ограничения, налагаемых на семафоры.
• SEM_NSEMS_MAX — это максимальное количество POSIX-семафоров, которые могут быть у одного процесса. В стандарте SUSv3 указано, что данное значение должно быть не меньше 256. В Linux количество семафоров ограничивается только объемом доступной памяти.
• SEM_VALUE_MAX — это максимальное значение, которого может достигнуть POSIX-семафор. Семафоры могут быть равны любому числу от 0 до SEM_VALUE_MAX. Стандарт SUSv3 требует, чтобы данное ограничение было не меньше 32 767; в Linux он равен INT_MAX (2 147 483 647 в Linux/x86-32).
POSIX-семафоры позволяют синхронизировать действия потоков или процессов. Они бывают двух видов: именованные и анонимные. Именованный семафор идентифицируется по имени и может разделяться между любыми процессами, у которых есть права на его открытие. Анонимный не имеет имени; для его разделения процессы или потоки должны поместить его на участок памяти, являющийся для них общим. В случае с процессами это может быть объект разделяемой памяти POSIX, а с потоками — глобальная переменная.
Интерфейс POSIX-семафоров отличается простотой. Выделение семафоров и работа с ними происходит на индивидуальном уровне, а инкрементация и декрементация изменяет их значение на 1.
POSIX-семафоры имеют ряд преимуществ перед своими аналогами из System V, но их сложнее переносить на другие системы. Для синхронизации в рамках многопоточных приложений более предпочтительными обычно являются мьютексы.
В [Stevens, 1999] приводится альтернативный взгляд на POSIX-семафоры и описывается их реализация в пользовательском пространстве на основе других IPC-механизмов (очередей FIFO, файлов, отображенных в память, и семафоров System V). Использование POSIX-семафоров в многопоточных приложениях демонстрируется в [Butenhof, 1996].
49.1. Отредактируйте программу из листинга 49.3 (psem_wait.c), заменив sem_wait() функцией sem_timedwait(). Программа должна принимать дополнительный аргумент командной строки, который определяет относительное время ожидания вызова sem_timedwait() (в секундах).
В предыдущих главах мы уже познакомились с разделяемыми файловыми отображениями, которые позволяют неродственным процессам взаимодействовать с помощью общих участков памяти (см. подраздел 45.4.2). Однако использование таких отображений для организации межпроцессного взаимодействия требует создания файла на диске, даже если мы не заинтересованы в постоянном резервном хранилище для разделяемого участка. Помимо неудобств, связанных с созданием файла, это приводит к дополнительному расходу ресурсов на ввод/вывод.