[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]

Программирование: Разработка и отладка программ


Использование взаимных блокировок

Взаимная блокировка - это взаимоисключающая блокировка. Захватить такую блокировку может только одна нить. Взаимные блокировки применяются для предотвращения одновременного доступа к общим данным. У взаимной блокировки есть атрибуты, которые определяют ее свойства. В текущей версии 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);

Для предотвращения тупиковых ситуаций такого типа всегда соблюдайте один и тот же порядок захвата взаимных блокировок.

Связанная информация

Рекомендации по созданию программ с нитями

Обзор синхронизации

Использование условных переменных

Стыковка нитей

Список процедур для поддержки синхронизации

Необязательные компоненты библиотеки работы с нитями


[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]