Олег Цилюрик - QNX/UNIX: Анатомия параллелизма
int pthread_mutexattr_setprotocol(
pthread_mutexattr_t* attr, int protocol);
int pthread_mutexattr_getprotocol(
pthread_mutexattr_t* attr, int* protocol);
Эти функции устанавливают/считывают протокол, который реализуется мьютексом для защиты от инверсии приоритетов. Переменная protocol может принимать следующие значения:
PTHREAD_PRIO_INHERIT (значение по умолчанию) — определяет, что для воспрепятствования возникновению инверсии приоритетов будет использоваться протокол наследования приоритетов.
PTHREAD_PRIO_PROTECT — любой поток, захвативший мьютекс и созданный с таким параметром, будет устанавливать фиксированный уровень приоритета в соответствии со значением поля prioceiling, возвращаемого функцией pthread_mutexattr_getprioceiling(). Таким образом, установка этого значения в качестве протокола мьютекса приводит к реализации протокола граничного приоритета для защиты от инверсии приоритетов.
Внешний доступ
int pthread_mutexattr_setpshared(
pthread_mutexattr_t* attr, int pshared);
int pthread_mutexattr_getpshared(
const pthread_mutexattr_t* attr, int* pshared);
Эти функции устанавливают/считывают внутреннее поле атрибутной записи мьютекса, определяющее, возможен ли доступ к мьютексу из потоков, запущенных вне процесса, в котором был создан и инициализирован мьютекс. Параметр pshared может принимать следующие значения:
PTHREAD_PROCESS_SHARED — любой поток из любого процесса в системе, который может получить доступ к синхронизирующему объекту (для этого придется использовать какой-либо из методов IPC, возможно shared memory), может использовать его по назначению.
PTHREAD_PROCESS_PRIVATE (значение по умолчанию) — мьютекс может использоваться только потоками, порожденными в том же процессе, где был инициализирован мьютекс. В документации сказано: попытка захвата мьютекса с таким значением параметра доступа к потокам из «чужого» процесса приведет к неопределенному результату. На практике же функция захвата возвращает управление в любом случае, независимо от того, был ли уже захвачен мьютекс другим потоком или нет (как будто происходит нормальный захват). Состояние мьютекса при этом никак не меняется.
Разрешение рекурсивного захвата
int pthread_mutexattr_setrecursive(
pthread_mutexattr_t* attr, int recursive);
int pthread_mutexattr_getrecursive(
const pthread_mutexattr_t* attr, int* recursive);
Функции устанавливают/считывают в атрибутной записи мьютекса признак, определяющий, может ли поток, ранее захвативший мьютекс (его владелец), захватить его еще раз (естественно, что любой другой поток захватить такой мьютекс уже не может и он будет заблокирован). Режим реализован для возможности рекурсивного вызова процедур в потоке. Необходимо помнить, что при рекурсивном захвате мьютекс должен быть освобожден столько раз, сколько раз он был захвачен. Параметр recursive может принимать следующие значения:
PTHREAD_RECURSIVE_ENABLE — разрешает рекурсивный захват мьютекса;
PTHREAD_RECURSIVE_DISABLE (значение по умолчанию) — запрещает рекурсивный захват мьютекса. В результате при попытке захвата мьютекса потоком, который им уже владеет, вызов pthread_mutex_lock() не приведет к захвату мьютекса и вернет значение EDEADLK.
Определение типа мьютекса
int pthread_mutexattr_settype(
pthread_mutexattr_t* attr, int type);
int pthread_mutexattr_gettype(
const pthread_mutexattr_t* attr, int* type);
В версиях QNX 6.2.1 и 6.3 предусматривается создание мьютексов следующих типов:
• PTHREAD_MUTEX_NORMAL — для этого типа не проводится контроль «мертвой блокировки» (deadlock) в ситуации, когда поток, захвативший мьютекс, пытается захватить его повторно. Поэтому при попытке повторного захвата такого мьютекса тем же потоком этот поток будет безусловно блокирован (то есть он попадает в «мертвую блокировку», а это во всех случаях аварийная ситуация в выполнении приложения); такой мьютекс уже некому разблокировать (мьютекс может разблокироваться только своим владельцем). Попытка освободить (unlock) мьютекс такого типа, захваченный другим потоком, или освободить незахваченный мьютекс ни к чему не приводит, при этом не возвращается ошибка выполнения.
• PTHREAD_MUTEX_ERRORCHECK — включается контроль ошибок. В этом режиме регистрируются следующие ситуации:
• попытка повторного захвата мьютекса тем же потоком;
• попытка освобождения мьютекса, захваченного другим потоком;
• освобождение свободного мьютекса.
• PTHREAD_MUTEX_RECURSIVE — мьютекс, допускающий рекурсивный захват. Поток, пытающийся захватить мьютекс, уже захваченный в этом потоке, сможет это сделать, при этом количество захватов будет учитываться при освобождении мьютекса. Другой поток сможет захватить такой мьютекс только тогда, когда он будет освобожден столько же раз, сколько был захвачен. Если поток пытается освободить мьютекс, захваченный другим потоком, или свободный мьютекс, то будет возвращено сообщение об ошибке (регистрируются ошибки, предусмотренные предыдущим типом, за исключением повторного захвата, который является для рекурсивного мьютекса штатным действием).
ПримечаниеОбратите внимание, что разрешение рекурсивного захвата мьютекса необходимо проводить установкой двух параметров (type и recursive).
• PTHREAD_MUTEX_DEFAULT (значение по умолчанию) — попытка рекурсивного захвата мьютекса, освобождения мьютекса, захваченного другим потоком, или освобождения уже свободного мьютекса ни к чему не приводит и не возвращает ошибку выполнения.
Освобождение параметров
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
Вызов разрушает ранее применявшийся объект - атрибутную запись мьютекса, после чего она уже не может более использоваться для инициализации мьютекса без предварительного выполнения вызова pthread_mutexattr_init().
На этом обсуждение атрибутов заканчивается, и мы переходим непосредственно к функциям работы с мьютексом.
Операции над мьютексом
Инициализация мьютекса
int pthread_mutex_init(pthread_mutex_t* mutex,
const pthread_mutexattr_t* attr);
Структура данных pthread_mutex_t определена в файле <pthread.h> (производный тип от типа sync_t, который в свою очередь определен в файле <target_nto.h>) и имеет следующий вид:
struct _sync_t {
/* Счетчик для рекурсивного мьютекса или семафора */
int count;
/* TID потока - имеет смысл и применяется только для мьютексов */
unsigned owner;
};
Функция pthread_mutex_init() инициализирует переданный объект мьютекс в соответствии со значением переданных атрибутов. Если вместо attr передать NULL, то мьютекс будет создан в соответствии со значениями атрибутов по умолчанию. В native QNX API эта функция реализуется вызовом SyncTypeCreate(). SyncTypeCreate() — единая функция для создания всех базовых объектов синхронизации QNX Neutrino.
Вместо прямого вызова функции pthread_mutex_init() для начальной инициализации статических мьютексов (глобальных на уровне файла кода или пространства имен namespace либо явно описанных с квалификатором static) можно воспользоваться двумя макросами PTHREAD_MUTEX_INITIALIZER и PTHREAD_RMUTEX_INITIALIZER:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_RMUTEX_INITIALIZER;
Первый из них создает мьютекс в соответствии со значениями атрибутов по умолчанию, а второй - мьютекс с разрешенным рекурсивным захватом.
Операции с граничным приоритетом
Большинство параметров мьютекса не могут быть изменены после его создания. Но не все. В процессе работы с мьютексом может быть изменено значение приоритета, которое система использует для реализации протокола граничного приоритета с целью предотвращения инверсии приоритетов:
int pthread_mutex_setprioceiling(pthread_mutex_t* mutex,
int prioceiling, int* old_ceiling);
int pthread_mutex_getprioceiling(const pthread_mutex_t* mutex,
int* prioceiling);
Функция pthread_mutex_setprioceiling() захватывает мьютекс (или блокируется, пока мьютекс не будет освобожден, и уже тогда захватывает его) и изменяет установленную для него величину граничного приоритета, после чего освобождает мьютекс для использования другими потоками. После изменения значения граничного приоритета предыдущее значение возвращается в old_ceiling.
Функция возвращает следующие значения:
EOK — успешное завершение;
EINVAL — указанный в вызове мьютекс не существует или указанный приоритет выходит за диапазон допустимых значений;
EPERM — поток, вызвавший функцию, не имеет прав на изменение граничного приоритета указанного мьютекса.