pthread_mutex_destroy, pthread_mutex_init

Имя

pthread_mutex_destroy, pthread_mutex_init — функции разруше н ия и инициализации мьютекса.

Синопсис

THR

#include <pthread.h>

int pthread_mutex_destroy (pthread_mutex_t *^utex);

int pthread_mutex_init (

pthread_mutex_t *restrict jnutex,

const pthread_mutexattr_t *restrict attr);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

Описание

Функция pthread_mutex_destroy () используется для разрушения объекта мьютекса, адресуемого параметром mutex, в результате чего этот объект мьютекса становится неинициализированным. В конкретной реализации функция pthread_mutex_destroy () может устанавливать объект, адресуемый параметром mutex, равным недействительному значению. Разрушенный объект мьютекса можно снова инициализировать с помощью функции pthread_mutex_init(); результаты ссылки на этот объект после его разрушения не определены.

Нет никакой опасности в разрушении инициализированного объекта мьютекса, по которому не заблокирован в данный момент ни один поток. Попытка же разрушить заблокированный мьютекс может привести к неопределенно м у поведению.

Функция pthread_mutex_init () используется для инициализации м ьютекса, адресуе м ого пара м етро м mutex, объекто м атрибутов, адресуемым параметром attr. Если параметр attr содержит значение NULL, для инициализации применяются атрибуты мьютекса, действующие по умолчанию, т.е. результат в этом случае равносилен передаче адреса объекта, содержащего стандартные атрибуты мьютекса. После успешной инициализации мьютекс становится инициализированным и разблокированным.

Для осуществления синхронизации используется только сам объект, адресуемый параметром mutex. Результат ссылки на копии объекта mutex в обращениях к функциям pthread_mutex_lock(), pthread_mutex_trylock(), pthread_mutex_unlock() и pthread_mutex_destroy () не определен.

Попытка инициализировать уже инициализированный объект мьютекса приведет к неопределенному поведению.

В случаях, когда атрибуты мьютекса, действующие по умолчанию, заранее определены, для инициализации мьютексов, которые создаются статически, можно использовать макрос PTHREAD_MUTEX_INITIALIZER. Резу л ьтат в этом с л учае эквивалентен динамической инициализации путем вызова функции pthread_mutex_init () с параметром attr, равным значению NULL, но без выпо л нения проверки на наличие ошибок.

Возвращаемые значения

При успешном завершении функции pthread_mutex_destroy() и pthread_ mutex_init () возвращают нулевое значение; в противном случае — код ошибки, обозначающий ее характер.

Проверка на наличие ошибок с кодами [EBUSY] и [EINVAL] реализована так (если реализована вообще), как будто она выполняется в самом начале работы каждой функции, и код ошибки в случае ее обнаружения возвращается до модификации состояния мьютекса, заданного параметром mutex.

Ошибки

Функция pthread_mutex_destroy () может завершиться неудачно, если:

[EBUSY]  реализация обнаружила попытку разрушить объект, адресуе м ый параметром mutex, который относится к другому потоку (напри м ер, при использовании в функциях pthread_mutex_wait () или pthread_mutex_timedwait ()), или указанный объект заблокирован;

[EINVAL] значение, заданное пара м етро м mutex, недействительно.

Функция pthread_mutex_init () завершится неудачно, если:

[EAGAIN] система испытывает недостаток ресурсов (не имеется в виду память), необходимых для инициализации еще одного мьютекса;

[ENOMEM] для инициализации мьютекса недостаточно существующей памяти;

[EPERM]  инициатор вызова функции не имеет привилегий для выполнения этой операции.

Функция pthread_mutex_init () м ожет завершиться неудачно, если:

[EBUSY]  реализация обнаружила попытку повторно инициализировать объект мьютекса, адресуемый параметром mutex, которой был ранее инициализирован, но еще не разрушен;

[EINVAL ] значение, заданное пара м етро м attr, недействительно. Эти функции не возвра щ ают код ошибки [EINTR].

Примеры

Отсутствуют.

Замечания по использованию

Отсутствуют.

Логическое обоснование

Возможность альтернативных реализаций

Данный том стандарта IEEE Std 1003.1-2001 поддерживает несколько альтернативных реализаций мьютексов. Реализация может сохранять блокировку непосредственно в объекте типа pthread_mutex_t. Возможно также хранение блокировки в «куче», а указателя, дескриптора или уникального ID — в объекте мьютекса. Каждая реализация обладает различными достоинствами в зависимости от определенных конфигураций оборудования. Поэтому, чтобы написать код, который не нужно будет изменять в зависимости от выбранной реализации, в данном томе стандарта IEEE Std 1003.1-2001 жестко не определяется тип хранения блокировки и термин «инициализировать» используется для усиления утверждения о том, что блокировка может в действительности располагаться в самом объекте мьютекса.

Обратите вни м ание на то, что это устраняет избыточность определения типа м ьютекса или условной пере м енной.

В реализации разрешается, чтобы при выполнении функции pthread_mutex_destroy() в мьютексе хранилось недействительное значение. Это позволит выявить ошибочные программы, которые пытаются заблокировать уже разрушенный мьютекс (или по крайней мере сослаться на него).

Компромисс между контролем за ошибками и производительностью

Существует множество случаев, когда можно обойтись без проверки на наличие ошибок ради достижения более высокой производительности. Полнота применения контроля за ошибками должна соответствовать нуждам конкретных приложений и возможностям сред выполнения. В общем случае об ошибках или ошибочных условиях, вызванных системными причинами (например, недостаточностью памяти), необходимо уведомлять всегда, но необязательно сообщать об ошибках, связанных с некорректностью кода приложения (например, при неудачной попытке обеспечить адекватную синхронизацию, используемую при защите мьютекса от удаления).

Таким образом, возможен широкий диапазон реализаций. Например, реализация, предназначенная для отладки приложений, может включать все возможные проверки ошибок, в то время как реализация, выполняющая на встроенном компьютере одно-единственное уже отлаженное приложение при очень строгих требованиях к производительности, может содержать лишь минимальный набор проверок на наличие ошибок. Более того, реализация может быть представлена даже в двух версиях подобно опциям, предоставляемым компиляторами: в версии с полным объемом проверок ошибок (но более медленной) и в версии с ограниченным объемом проверок ошибок (но более быстрой). Запретить возможность необязательности контроля за ошибками значило бы оказать пользователю медвежью услугу.

Предусмотрительно ограничивая использование понятия «неопределенное поведение» только случаями ошибочных действий самого приложения (по причине недостаточно продуманного кода) и обязательно определяя ошибки, связанные с недоступностью системных ресурсов, данный том стандарта IEEE Std 1003.1-2001 гарантирует, что любое корректно написанное приложение переносимо в полном диапазоне реализаций, но не обязывает все реализации нести дополнительные затраты на проверку многочисленных условий, которые корректно написанная программа никогда не создаст.

Почему не определяются предельные значения

Определение символьных значений для использования в качестве максимального числа мьютексов и условных переменных рассматривалось, но было отвергнуто, поскольку количество этих объектов может изменяться динамически. Более того, многие реализации размещают эти объекты в памяти приложения, следовательно, говорить о необходимости явного определения максимума нет никакого смысла.

Статические инициализаторы для мьютексов и условных переменных

Обеспечение статической инициализации статически размещаемых в памяти объектов синхронизации позволяет в модулях, содержащих закрытые статические переменные синхронизации, избежать тестирования и соответствующих затрат, связанных с динамической инициализацией. Более того, это упрощает кодирование модулей самоинициализации . Такие модули широко используются в библиотеках, в которых по различным причинам вместо явного вызова функций инициализации используется самоинициализация. Ниже приводится пример использования статической инициализации.

Без применения статической инициализации функция самоинициализации foo () может иметь следующий вид.

static pthread_once_t foo_once = PTHREAD_ONCE_INIT;

static pthread_mutex_t foo_mutex;

void foo_init () {

pthread_mutex_init (&foo_mutex, NULL);

}

void foo() {

pthread_once(&foo_once, foo_init);

pthread_mutex_lock (&foo_mutex);

/* Выполнение действий. */

pthread_mutex_unlock (&foo_mutex);

}

С применением статической инициализации ту же функцию самоинициализации foo() м ожно было бы закодировать таки м образо м.

static pthread_mutex_t foo_mutex = PTHREAD_MUTEX_INITIALIZER;

void foo()

{

pthread_mutex_lock(&foo_mutex) ;

/* Выполнение действий. */

pthread_mutex_unlock(&foo_mutex);

}

Обратите внимание на то, что статическая инициализация устраняет необходимость в тестировании, проводимом в функции pthread_once (), и получении значения адреса &foo_mutex, передаваемого функции pthread_mutex_lock() или pthread_mutex_unlock ().

Таким образом, С-код, написанный для инициализации статических объектов, проще во всех системах и работает быстрее на большом классе систем, в которых объект (внутренней) синхронизации можно хранить в памяти приложения.

До сих пор вопрос о быстродействии блокировок поднимался для машин, которые требовали, чтобы для мьютексов выделялась специальная память. В действительности в таких машинах мьютексы и, возможно, условные переменные должны были содержать указатели на реальные аппаратные средства защиты. Для того чтобы на таких машинах работала статическая инициализация, функция pthread_mutex_lock () также должна проверять, выделена ли память для указателя на реальный объект блокировки. Если не выделена, функция pthread_mutex_lock (), прежде чем его использовать, должна его инициализировать. Резервирование таких ресурсов можно выполнить при загрузке программы, и поэтому для мьютексов и условных переменных не были введены дополнительные коды ошибок, означающие неудачное выполнение инициализации.

Такое динамическое тестирование в функции pthread_mutex_lock(), которое позволяет узнать, был ли инициализирован указатель, могло показаться на первый взгляд лишним. На большинстве компьютеров это было бы реализовано в виде считывания его значения, сравнения с нулем и использования по назначению при условии получения нужного результата сравнения. Несмотря на то что это тестирование кажется лишним, дополнительные затраты (на тестирование содержимого регистра) обычно незначительны, поскольку в действительности никакие дополнительные ссылки на память не делаются. Так как все больше и больше компьютеров оснащаются кэш-памятью (быстродействующей буферной памятью большой емкости), то реальные издержки представляют собой отработку ссылок, а не выполнение инструкций.

В качестве альтернативного варианта (в зависимости от архитектуры компьютера) можно в наиболее важных случаях ликвидировать все расходы системных ресурсов на операции блокировки, которые выполняются после инициализации средств блокировки. Это можно сделать путем перехода от более затратных к редко выполняемым операциям, т.е. перенести весь «груз расходов» на однократно выполняемую инициализацию. Поскольку «внешняя» (т.е. выполняемая вне основной программы) инициализация мьютекса также означает, что для получения реальной блокировки адрес должен быть разыменован, один из широко применяемых методов при статической инициализации состоит в сохранении фиктивного значения для этого адреса; в частности, адреса, который вызывает сбой в работе компьютера. При возникновении такого сбоя во время первой попытки заблокировать мьютекс можно сделать проверку достоверности, а затем для реальной блокировки использовать корректный адрес. Последующие операции, связанные с блокировкой, не будут сопряжены с дополнительными расходами, поскольку они уже не являются «сбойными». Это — всего лишь метод, который можно использовать для поддержки статической инициализации, несмотря на то, что он неблагоприятно отражается на скорости захвата блокировки. Безусловно, существуют и другие методы, которые в высокой степени зависят от архитектуры компьютера.

Расходы на блокировку для компьютеров, выполняющих «внешнюю» инициализацию мьютекса, сравнимы с расходами для модулей, инициализируемых неявным образом (имеются в виду те из них, где достигнута «внутренняя» инициализация мьютексов). Безусловно, «внутренняя» инициализация выполняется гораздо быстрее, но «внешняя» ненамного хуже.

Помимо вопроса быстродействия блокировки, нас беспокоит то, что потоки могут соперничать за блокировки при попытке завершить инициализацию статически размещаемых в памяти мьютексов. (Такое завершение обычно включает захват внутренней блокировки, выделение памяти для структуры, сохранение указателя на эту структуру в мьютексе и освобождение внутренней блокировки.) Во-первых, многие реализации могут сократить эту последовательность действий путем хеширования по адресу мьютекса. Во-вторых, количество таких «сериалов» может быть весьма ограниченным. В частности, их может быть столько, сколько создается статически размещаемых объектов синхронизации. Динамически же создаваемые объекты по-прежнему инициализируются с помощью функций pthread_mutex_init () или pthread_cond_init ().

Наконец, если ни один из описанных выше методов оптимизации для «внешнего» размещения объектов синхронизации не позволяет достичь нужной производительности приложения при использовании определенной реализации, приложение может избежать статической инициализации, явным образом инициализируя все объекты синхронизации c помощью соответствующих функций pthread_*_init(), которые поддерживаются всеми реализациями. В документации на реализацию также могут быть описаны компромиссные решения и рекомендации относительно того, какие методы инициализации являются наиболее эффективными для данной конкретной реализации.

Разрушение мьютексов

Мьютекс можно разрушить сразу после разблокировки. Например, рассмотрим следующий код.

struct obj {

pthread_mutex_t om;

int refcnt;

};

obj_done (struct obj *op) {

pthread_mutex_lock (&op- >om);

if (—op- >refcnt == 0) {

pthread_mutex_unlock (&op- >om);

(A) pthread_mutex_destroy (&op- >om);

(B) free(op); } else

(С) pthread_mutex_unlock (&op->om);

}

В данном случае структура obj служит для учета количества ссылок, а функция obj_done() вызывается всякий раз, когда удаляется ссылка на объект. Реализации должны позволить разрушение объекта и освобождение занимаемых им ресурсов (см. строки А и В) сразу после его разблокировки (строка С).

Будущие направления

Отсутствуют.

Смотри также

pthread_mutex_getprioceiling (), pthread_mutex_lock (), pthread_mutex_timedlock (), pthread_mutexattr_getpshared (), том Base Definitions стандарта IEEE Std 1003.1-2001, <pthread.h>.

Последовательность внесения изменений

Функции впервые реализованы в выпуске Issue 5. Включены для согласования с расширение м POSIX Threads Extension.

Issue 6

Функции pthread_mutex_destroy() и pthread_mutex_init() от м ечены как часть опции Threads.

В целях согласования со стандарто м IEEE Std 1003.1d-1999 в раздел «С м отри также» была добавлена функция pthread_mutex_timedlock ().

Раздел «Описание» б ы л отредактирован путе м при м енения интерпретации IEEE PASC Interpretation 1003.1с #34.

В целях согласования со стандартом ISO/IEC 9899: 1999 в прототип функции pthread_mutex_init () было добавлено ключевое слово restrict.

Больше книг — больше знаний!

Заберите 30% скидку новым пользователям на все книги Литрес с нашим промокодом

ПОЛУЧИТЬ СКИДКУ