Взаимная блокировка - это взаимоисключающая блокировка. Захватить такую блокировку может только одна нить. Взаимные блокировки применяются для предотвращения одновременного доступа к общим данным. У взаимной блокировки есть атрибуты, которые определяют ее свойства. В текущей версии AIX атрибуты взаимных блокировок не применяются. Следовательно, при создании взаимной блокировки ее объект атрибутов можно проигнорировать.
Следующие разделы посвящены работе со взаимными блокировками:
Как и нити, взаимные блокировки создаются с помощью объектов атрибутов. Объект атрибутов взаимной блокировки представляет собой абстрактный объект, содержащий несколько атрибутов, набор которых зависит от реализации компонентов POSIX. Для работы с объектом атрибутов создается переменная типа pthread_mutexattr_t. В AIX тип данных pthread_mutexattr_t - это указатель; в других системах он может быть структурой или каким-то другим типом данных.
Функция pthread_mutexattr_init инициализирует объект атрибутов взаимной блокировки значениями по умолчанию. Для работы с атрибутами предназначены специальные функции. Для удаления объекта атрибутов взаимной блокировки предназначена функция pthread_mutexattr_destroy. В некоторых реализациях библиотеки нитей эта функция освобождает память, динамически выделенную функцией pthread_mutexattr_init.
В приведенном ниже примере объект атрибутов создается и инициализируется значениями по умолчанию. Когда объект становится ненужным, он уничтожается.
pthread_mutexattr_t attributes; /* создается объект атрибутов */ ... if (!pthread_mutexattr_init(&attributes)) { /* объект атрибутов инициализируется */ ... /* работа с объектом атрибутов */ ... pthread_mutexattr_destroy(&attributes); /* удаление объекта атрибутов */ }
С помощью одного объекта атрибутов можно создать несколько взаимных блокировок. Кроме того, объект атрибутов можно изменить в промежутке между созданием двух взаимных блокировок. После создания взаимных блокировок объект атрибутов можно удалить - это никак не отразится на созданных взаимных блокировках.
В AIX атрибуты взаимной блокировки не определены. Для этого
необходимы опции POSIX (Необязательные компоненты библиотеки работы с нитями), не реализованные в системе AIX. Однако в других
системах можно определить следующие атрибуты:
Protocol | Задает протокол, защищающий приоритеты нитей от изменения после установки взаимной блокировки. Этот атрибут может принимать значение протокола защиты приоритетов или протокола наследования, реализованные в POSIX (Необязательные компоненты библиотеки работы с нитями). |
Prioceiling | Задает максимальный приоритет взаимной блокировки. Значение этого атрибута зависит от компонента защиты приоритета POSIX (Необязательные компоненты библиотеки работы с нитями). |
Process-shared | Задает режим совместного использования взаимной блокировки. Значение этого атрибута зависит от опций POSIX совместного использования процесса (Необязательные компоненты библиотеки работы с нитями). |
В большинстве случаев не требуется изменять значения этих атрибутов, установленные по умолчанию. Дополнительная информация о протоколе защиты и атрибутах prioceiling приведена в разделе Планирование при синхронизации; об атрибуте process-shared - в разделе Расширенные атрибуты.
Взаимная блокировка создается вызовом функции pthread_mutex_init. При вызове этой функции можно указать объект атрибутов взаимной блокировки. Если значение указателя будет равно NULL, атрибутам будут присвоены значения по умолчанию. Следовательно, фрагмент кода:
pthread_mutex_t mutex; pthread_mutex_attr_t attr; ... pthread_mutexattr_init(&attr); pthread_mutex_init(&mutex, &attr); pthread_mutexattr_destroy(&attr);
эквивалентен следующему:
pthread_mutex_t mutex; ... pthread_mutex_init(&mutex, NULL);
ИД созданной взаимной блокировки возвращается нити через параметр mutex. ИД взаимной блокировки представляет собой объект защищенного типа pthread_mutex_t. В AIX тип данных pthread_mutex_t - структура; в других системах это может быть указатель или другой тип данных.
Взаимная блокировка должна создаваться один раз. Не следует вызывать функцию pthread_mutex_init с тем же параметром mutex несколько раз (например, в двух разных нитях, одновременно выполняющих один и тот же код). В результате повторного вызова будет возвращен код ошибки EBUSY. Предотвратить повторное создание взаимной блокировки можно тремя способами:
Когда взаимная блокировка становится ненужной, ее нужно удалить с помощью функции pthread_mutex_destroy. Эта функция освобождает все области памяти, выделенные функцией pthread_mutex_init. После удаления взаимной блокировки ту же самую переменную pthread_mutex_t можно вновь использовать для создания другой взаимной блокировки. Например, приведенный ниже фрагмент кода, хотя и несколько искусственный, не содержит ошибок:
pthread_mutex_t mutex; ... for (i = 0; i < 10; i++) { /* создание взаимной блокировки */ pthread_mutex_init(&mutex, NULL); /* применение взаимной блокировки */ /* удаление взаимной блокировки */ pthread_mutex_destroy(&mutex); }
Как и другие общие системные ресурсы, взаимные блокировки, размещенные в стеке нити, необходимо удалить до завершения нити. В библиотеке нитей хранится скомпонованный список взаимных блокировок; следовательно, при освобождении стека, в котором хранится взаимная блокировка, этот список будет поврежден.
Взаимная блокировка - это обычная блокировка, у которой есть два состояния: занята и свободна. После создания взаимная блокировка свободна. Для захвата взаимной блокировки применяется функция pthread_mutex_lock:
Функция pthread_mutex_trylock работает так же, как и функция pthread_mutex_lock, но не блокирует вызывающую нить:
Нить, захватившую взаимную блокировку, часто называют владельцем блокировки.
Функция pthread_mutex_unlock освобождает взаимную блокировку, если ее владельцем является вызывающая нить:
Поскольку при захвате взаимной блокировки точка завершения не образуется (см. Точки завершения), то нить, заблокированную в ожидании взаимной блокировки, невозможно принудительно завершить (см. Принудительное завершение работы нити). В связи с этим рекомендуется устанавливать взаимные блокировки на короткое время, например, для защиты общих данных.
Взаимные блокировки либо устанавливаются для защиты данных, либо служат основой для создания более сложных объектов синхронизации. Дополнительная информация о реализации долговременных блокировок и блокировок чтения и записи с приоритетом приведена в разделе Создание сложных объектов синхронизации.
Взаимные блокировки позволяют защитить данные от параллельного изменения несколькими нитями. Например, приложение для работы с базой данных может создать несколько нитей для одновременной обработки нескольких запросов. При этом сама база данных будет защищена взаимной блокировкой db_mutex.
/* главная нить */ pthread_mutex_t mutex; int i; ... pthread_mutex_init(&mutex, NULL); /* создание взаимной блокировки */ for (i = 0; i < num_req; i++) /* создание нитей в цикле */ pthread_create(th + i, NULL, rtn, &mutex); ... /* ожидание конца сеанса */ pthread_mutex_destroy(&mutex); /* удаление взаимной блокировки */ ...
/* нить, обрабатывающая запрос */ ... /* ожидание запроса */ pthread_mutex_lock(&db_mutex); /* блокировка базы данных */ ... /* обработка запроса */ pthread_mutex_unlock(&db_mutex); /* разблокирование базы данных */ ...
Главная нить создает взаимную блокировку и все нити, предназначенные для обработки запросов. Взаимная блокировка передается нити через параметр функции точки входа нити. В реальной программе адрес взаимной блокировки может передаваться во вновь созданную нить в виде одного из полей сложной структуры данных.
В AIX взаимные блокировки не могут несколько раз захватываться одной и той же нитью. В некоторых других системах это разрешается. При создании переносимых программ следует исходить из того, что приведенный ниже фрагмент кода может вызвать тупиковую ситуацию:
pthread_mutex_lock(&mutex); pthread_mutex_lock(&mutex);
Тупиковая ситуация может возникнуть в случае, если после захвата взаимной блокировки выполняется вызов функции, которая захватывает эту же взаимную блокировку. Например:
pthread_mutex_t mutex; struct { int a; int b; int c; } A;
f() { pthread_mutex_lock(&mutex); /* 1-й вызов */ A.a++; g(); A.c = 0; pthread_mutex_unlock(&mutex); }
g() { pthread_mutex_lock(&mutex); /* 2-й вызов */ A.b += A.a; pthread_mutex_unlock(&mutex); /* 3-й вызов */ }
В некоторых других системах вызов функции f может привести к тупику: 2-й вызов блокирует нить, так как 1-й вызов уже захватил взаимную блокировку. В AIX этот фрагмент кода тоже не всегда выполняется очевидным образом. 2-й вызов может завершиться неудачей, но 3-й вызов может быть успешным. Следовательно, при возврате из функции g взаимная блокировка может быть уже освобождена, т.е. переменная A, возможно, уже не защищена; при возврате из функции f значение переменной A.c может быть отлично от нуля.
Для предотвращения тупиковых ситуаций и сохранения целостности данных рекомендуется воспользоваться одной из следующих схем:
Тупиковые ситуации могут возникать при захвате взаимных блокировок в обратном порядке. Например, в приведенном ниже фрагменте кода выполнение нитей A и B может зайти в тупик:
/* Нить A */ pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2);
/* Нить B */ pthread_mutex_lock(&mutex2); pthread_mutex_lock(&mutex1);
Для предотвращения тупиковых ситуаций такого типа всегда соблюдайте один и тот же порядок захвата взаимных блокировок.
Рекомендации по созданию программ с нитями
Использование условных переменных
Список процедур для поддержки синхронизации
Необязательные компоненты библиотеки работы с нитями