Если сразу несколько потоков попытаются выполнить этот блок кода (критический участок), только один из них сможет получить этот мьютекс (другие останутся заблокированными); это означает, что в заданный момент времени только один поток сможет войти в этот блок, как проиллюстрировано на рис. 30.2.

Рис. 30.2.Использование мьютекса для защиты критического участка

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

30.1.1. Статически выделяемые мьютексы

Мьютекс должен быть выделен в виде статической переменной или создан динамически во время выполнения программы (например, в блоке памяти выделенном с помощью вызова malloc()). Динамическое создание мьютексов является несколько более сложным, поэтому мы отложим его обсуждение до подраздела 30.1.5.

Мьютекс представляет собой переменную типа pthread_mutex_t. Прежде чем вы сможете его использовать, он должен быть инициализирован. В случае со статически выделенным мьютексом это можно сделать путем присваивания ему значения PTHREAD_MUTEX_INITIALIZER, как показано ниже:

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

Согласно стандарту SUSv3 применение операций, которые будут описаны в оставшейся части этого раздела, к копии мьютекса приводит к неопределенным результатам. Эти операции всегда должны выполняться с оригиналом мьютекса, инициализированным статически, с помощью значения PTHREAD_MUTEX_INITIALIZER, или динамически, путем вызова pthread_mutex_init() (описанного в разделе 30.1.5).

30.1.2. Закрытие и открытие мьютекса

После инициализации мьютекс находится в открытом состоянии. Для его закрытия и открытия используются функции pthread_mutex_lock() и pthread_mutex_unlock().

#include

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Обе возвращают 0 при успешном завершении или положительное число, если случилась ошибка

Чтобы закрыть мьютекс, его нужно передать в вызов pthread_mutex_lock(). Если он в данный момент открыт, вызов сразу же возвращается. Если же мьютекс закрыт другим потоком, pthread_mutex_lock() блокируется, пока тот не откроется; в момент открытия данная функция снова блокирует мьютекс и возвращается.

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

Функция pthread_mutex_unlock() открывает мьютекс, закрытый ранее вызывающим потоком. Если мьютекс уже открыт или был закрыт другим потоком, данная функция возвращает ошибку.

Если мьютекс, открытый pthread_mutex_unlock(), ожидают сразу несколько потоков, получить его может любой из них.

Пример программы

Ниже представлена измененная версия листинга 30.1, которая использует мьютекс для защиты доступа к глобальной переменной glob. Запустив эту программу с тем же аргументом командной строки, что и раньше, мы увидим, что инкрементация значения glob всегда происходит предсказуемо:

$ ./thread_incr_mutex 10000000

glob = 20000000

Листинг 30.2. Использование мьютекса для защиты доступа к глобальной переменной

threads/thread_incr_mutex.c

#include

#include "tlpi_hdr.h"

static int glob = 0;

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static void * /* 'arg' раз инкрементируем 'glob' */

threadFunc(void *arg)

{

int loops = *((int *) arg);

int loc, j, s;

for (j = 0; j < loops; j++) {

s = pthread_mutex_lock(&mtx);

if (s!= 0)

errExitEN(s, "pthread_mutex_lock");

loc = glob;

loc++;

glob = loc;

s = pthread_mutex_unlock(&mtx);

if (s!= 0)

errExitEN(s, "pthread_mutex_unlock");

}

return NULL;

}

int

main(int argc, char *argv[])

{

pthread_t t1, t2;

int loops, s;

loops = (argc > 1)? getInt(argv[1], GN_GT_0, "num-loops"): 10000000;

s = pthread_create(&t1, NULL, threadFunc, &loops);

if (s!= 0)

errExitEN(s, "pthread_create");

s = pthread_create(&t2, NULL, threadFunc, &loops);

if (s!= 0)

errExitEN(s, "pthread_create");

s = pthread_join(t1, NULL);

if (s!= 0)

errExitEN(s, "pthread_join");

s = pthread_join(t2, NULL);

if (s!= 0)

errExitEN(s, "pthread_join");

printf("glob = %d\n", glob);

exit(EXIT_SUCCESS);

}

Перейти на страницу:

Похожие книги