Новосибирский Государственный Университет
Опубликован: 28.04.2010 | Доступ: свободный | Студентов: 686 / 60 | Оценка: 4.62 / 4.24 | Длительность: 10:21:00
Лекция 6:

Мутексы

< Лекция 5 || Лекция 6: 123 || Лекция 7 >

Атрибуты мутексов

Как и pthread_attr_t, pthread_mutex_attr_t представляет собой непрозрачный тип данных, содержащий набор атрибутов мутекса. Для просмотра и изменения этих атрибутов необходимо использовать get/set функции. Как и у pthread_attr_t, между атрибутами и мутексом не образуется никакой постоянной связи. Если завести переменную pthread_attr_t_attr и создать по ней мутекс, последующие изменения attr не окажут никакого влияния на уже созданный мутекс. Большинство атрибутов мутекса вообще не могут быть изменены после создания мутекса.

Список атрибутов мутекса таков:

  • pshared
  • type
  • protocol
  • prioceiling
  • robust_np (нестандартный)

Атрибут pshared определяет область действия мутекса. Допустимые значения - PTHREAD_PROCESS_SHARED (разделяемый мутекс, который может использоваться для межпроцессного взаимодействия) и PTHREAD_PROCESS_PRIVATE (локальный или приватный мутекс, пригодный только для синхронизации нитей одного процесса). Значение по умолчанию - PTHREAD_PROCESS_PRIVATE. Чтобы использовать разделяемый мутекс для межпроцессного взаимодействия, его необходимо разместить в сегменте разделяемой памяти, например в файле, отображенном на память с флагом MAP_SHARED, или в разделяемой памяти System V IPC.

Атрибут type обозначает способ проверки ошибок при работе с мутексом. Допустимые значения - PTHREAD_MUTEX_NORMAL, PTHREAD_MUTEX_ERRORCHECK, PTHREAD_MUTEX_RECURSIVE и PTHREAD_MUTEX_DEFAULT. Мутексы типа NORMAL не делают никаких проверок. Многократный захват мутекса одной нитью приводит к мертвой блокировке, результаты многократного освобождения мутекса или захвата мутекса одной нитью и освобождения другой не определены. На практике, в Solaris 10 и в Linux попытки освобождения свободного мутекса игнорируются, а попытка освобождения мутекса, захваченного другой нитью, приводит к тому, что мутекс переводится в состояние "свободен" и одна из заблокированных на нем нитей освобождается. Таким образом, в некоторых реализациях мутексы типа NORMAL ведут себя скорее как двоичные семафоры, чем как мутексы в строгом смысле этого слова. Но стандарт POSIX не гарантирует, что мутексы будут вести себя именно таким образом во всех реализациях, в том числе и в будущих версиях Linux и Solaris.

Тип ERRORCHECK требует, чтобы все операции над мутексами проверяли состояние мутекса и возвращали ошибки при недопустимых последовательностях операций над мутексом. Из описания pthread_attr_settype(3C) можно сделать вывод, что такие мутексы делают проверку на мертвую блокировку с участием нескольких нитей, но это не так. В соответствии с требованиями стандарта, код ошибки EDEADLK возвращается только при попытке захвата мутекса, уже занятого текущей нитью. Мутексы типа ERRORCHECK удобны для отладки приложений, но требуют гораздо большего объема вычислений, чем мутексы типа NORMAL.

Мутексы типа RECURSIVE допускают многократный захват одной и той же нитью. Такой мутекс поддерживает счетчик захватов и должен быть освобожден нитью столько же раз, сколько был захвачен. Такие мутексы удобны для борьбы с мертвыми блокировками, возникающими в коде, аналогичном примеру 1. Попытка освобождения свободного мутекса или мутекса, занятого другой нитью, приводит к ошибке.

Тип DEFAULT в соответствии со стандартом делает даже меньше проверок, чем тип NORMAL. Так, повторный захват мутекса типа NORMAL приводит к мертвой блокировке, а повторный захват мутекса типа DEFAULT, в соответствии со стандартом, может приводить к непредсказуемым последствиям вплоть до аварийного завершения процесса. Реализация имеет право отображать тип DEFAULT на любой другой тип. Solaris 10 отображает тип DEFAULT на тип NORMAL.

Атрибут protocol описывает схему предотвращения инверсии приоритета, используемую этим мутексом. Инверсия приоритета подробнее рассматривается в приложении к этой лекции. Это проблема, которая возникает, если низкоприоритетная нить удерживает мутекс, на котором ожидает высокоприоритетная нить. По умолчанию, мутексы создаются с протоколом PTHREAD_PRIO_NONE, т.е. не предоставляют никаких средств для борьбы с инверсией приоритета. Два других допустимых значения protocol - это PTHREAD_PRIO_INHERIT и PTHREAD_PRIO_PROTECT. INHERIT обозначает наследование приоритета: приоритет нити, удерживающей мутекс, повышается до самого высокого из приоритетов нитей, ждущей этого мутекса. PROTECT обозначает так называемый "потолок приоритета" (priority ceiling): приоритет процесса, удерживающего мутекс, равен приоритету, указанному в свойствах этого мутекса, а именно - в свойстве prioceiling. Приоритет мутекса вообще говоря не зависит от приоритетов нитей, ждущих на этом мутексе. Если нить удерживает несколько мутексов с протоколами INHERIT и PROTECT, ее приоритет будет равен наивысшему из приоритетов, обеспечиваемых этими мутексами.

Атрибут prioceiling используется мутексами с протоколом PTHREAD_PRIO_PROTECT. При остальных значениях атрибута protocol этот атрибут игнорируется.

Атрибут robust_np используется главным образом с межпроцессными мутексами ( pshared==PROCESS_SHARED ). Это нестандартный атрибут, реализованный в Solaris; в Linux этот атрибут появился в версии ядра 2.6 и NPTL; большинство других реализаций POSIX Threads API его не поддерживают. Суффикс _np у имени атрибута сигнализирует, что этот атрибут не является частью стандарта POSIX. Как ни странно, в соответствии со страницей pthread_attr_setrobust_np(3C) системного руководства Solaris 10, для использования атрибута robust_np необходимо установить атрибут protocol равным POSIX_PRIO_INHERIT, а pshared может принимать любое значение.

Атрибут robust_np управляет поведением мутекса в ситуации, когда владелец этого мутекса аварийно завершился. Под аварийным завершением подразумевается не pthread_cancel(3С), а завершение процесса по сигналу. Завершение процесса по сигналу приводит к завершению всех нитей этого процесса; локальные мутексы и защищаемые ими ресурсы при этом уничтожаются и их судьба никого не интересует. Однако при использовании разделяемой памяти и разделяемых мутексов возможна ситуация, когда один из процессов завершается, а остальные продолжают работу. Если одна из нитей завершившегося процесса удерживала мутекс, то нити остальных процессов, пытающиеся захватить этот мутекс, могут остаться в состоянии бесконечного ожидания. Эта проблема не может быть решена за счет принудительного освобождения мутекса, ведь занятость мутекса сигнализирует, что защищаемый им ресурс, скорее всего, находится в несогласованном состоянии.

Атрибут robust_np может принимать два значения: PTHREAD_MUTEX_STALLED_NP и PTHREAD_MUTEX_ROBUST_NP. Значение по умолчанию - PTHREAD_MUTEX_STALLED_NP. При этом мутексы, удерживавшиеся завершившимся процессом, просто остаются в занятом состоянии, и все нити, пытающиеся захватить их, блокируются.

В режиме ROBUST (надежный) мутекс реализует более сложное поведение. Первая нить, пытающаяся захватить мутекс, получает ошибку EOWNERDEAD, но мутекс при этом захватывается. Нить должна попытаться привести ресурс, защищаемый мутексом, в согласованное состояние. Если это удается, нить должна вызвать функцию pthread_mutex_consistent_np(3C). После этого остальные нити смогут продолжить нормальную работу с мутексом и ресурсом. Если восстановить согласованное состояние ресурса не удается, нить должна просто освободить мутекс. После этого все попытки захватить такой мутекс будут завершаться с кодом ошибки ENOTRECOVERABLE.

< Лекция 5 || Лекция 6: 123 || Лекция 7 >