30.1.7. Типы мьютексов
На предыдущих страницах приводится ряд утверждений о поведении мьютексов.
• Поток не может закрывать один и тот же мьютекс дважды.
• Поток не может открыть мьютекс, которым он не владеет (то есть мьютекс, который он не закрывал).
• Поток не может открыть мьютекс, который не является закрытым.
Что именно происходит в каждом из этих случаев, зависит от
• PTHREAD_MUTEX_NORMAL — этот тип мьютексов не поддерживает обнаружение взаимного блокирования. Если поток попытается закрыть мьютекс, который он уже закрыл, его работа будет заблокирована. Открытие мьютекса, который не является закрытым или закрыт другим потоком, приводит к неопределенным результатам (в Linux обе эти операции с данным типом мьютекса заканчиваются успешно).
• PTHREAD_MUTEX_ERRORCHECK — проверка ошибок выполняется для всех операций. Все три сценария, перечисленные выше, приводят к возврату ошибок соответствующими функциями из состава Pthreads. Этот тип мьютексов обычно отличается пониженной производительностью, но может пригодиться во время отладки, позволяя найти те участки приложения, которые нарушают правила использования мьютексов.
• PTHREAD_MUTEX_RECURSIVE — рекурсивный мьютекс реализует концепцию счетчика закрытий. Когда поток впервые получает мьютекс, счетчику устанавливается значение 1. При каждой следующей операции закрытия тем же потоком счетчик инкрементируется, а при открытии — декрементируется. Мьютекс освобождается (то есть становится доступным для других потоков), только когда счетчик закрытий достигает значения 0. Открытие мьютекса, который не является закрытым или закрыт другим потоком, заканчивается неудачей.
В Linux реализация потоков поддерживает нестандартные статические инициализаторы для каждого из типов мьютексов, приведенных выше (например, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP), поэтому для инициализации статически выделенных мьютексов не требуется вызывать функцию pthread_mutex_init(). Однако в переносимых приложениях следует воздержаться от использования этих инициализаторов.
Помимо типов, описанных выше, стандарт SUSv3 определяет дополнительный тип, PTHREAD_MUTEX_DEFAULT, который устанавливается по умолчанию в случае использования инициализатора PTHREAD_MUTEX_INITIALIZER или если мы указали NULL для аргумента attr в функции pthread_mutex_init(). Поведение этих типов мьютексов намеренно оставлено неопределенным во всех трех сценариях, описанных в начале данного раздела; это обеспечивает максимальную гибкость при реализации эффективных мьютексов. В Linux поведение мьютексов PTHREAD_MUTEX_DEFAULT и PTHREAD_MUTEX_NORMAL является идентичным.
В листинге 30.3 показано, как задать тип мьютекса (в этом примере мы используем мьютекс
Листинг 30.3. Задание типа мьютекса
pthread_mutex_t mtx;
pthread_mutexattr_t mtxAttr;
int s, type;
s = pthread_mutexattr_init(&mtxAttr);
if (s!= 0)
errExitEN(s, "pthread_mutexattr_init");
s = pthread_mutexattr_settype(&mtxAttr, PTHREAD_MUTEX_ERRORCHECK);
if (s!= 0)
errExitEN(s, "pthread_mutexattr_settype");
s = pthread_mutex_init(mtx, &mtxAttr);
if (s!= 0)
errExitEN(s, "pthread_mutex_init");
s = pthread_mutexattr_destroy(&mtxAttr); /* Больше не требуется */
if (s!= 0)
errExitEN(s, "pthread_mutexattr_destroy");
Мьютекс предотвращает одновременный доступ к переменной из разных потоков. Условные переменные позволяют потокам информировать друг друга об изменениях в состоянии разделяемых переменных (или других общих ресурсов) и ждать (блокируясь) получения таких уведомлений.
Ниже показан простой пример без задействования условных переменных, который призван продемонстрировать их пользу. Представьте, что у вас есть набор потоков, которые генерируют некоторые «итоговые элементы», потребляемые главным потоком. Количество полученных элементов, ожидающих потребления, будет представлено с помощью переменной avail, защищенной мьютексом:
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int avail = 0;
Участки кода, представленные в данном разделе, можно найти в файле threads/prod_no_condvar.c, который входит в состав архива с исходным кодом, прилагающимся к этой книге.
Потоки, генерирующие элементы, будут состоять примерно из такого кода:
/* Код для генерирования элементов опущен */
s = pthread_mutex_lock(&mtx);
if (s!= 0)
errExitEN(s, "pthread_mutex_lock");
avail++; /* Уведомляем потребителя о готовности еще одного элемента */
s = pthread_mutex_unlock(&mtx);
if (s!= 0)
errExitEN(s, "pthread_mutex_unlock");
А в главном потоке (потребителе) мы воспользуемся следующим кодом:
for (;;) {
s = pthread_mutex_lock(&mtx);
if (s!= 0)
errExitEN(s, "pthread_mutex_lock");
while (avail > 0) { /* Потребляем все доступные элементы */
/* Делаем что-нибудь со сгенерированным элементом */
avail-;
}
s = pthread_mutex_unlock(&mtx);
if (s!= 0)