Выбрать главу

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

■ В цикле необходимо захватить исключающий семафор и прочитать значение флага.

■ Если флаг установлен, нужно разблокировать семафор и выполнить требуемые действия.

■ Если флаг не установлен, одновременно выполняются операции освобождения семафора и перехода в режим ожидания сигнала.

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

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

■ Функция pthread_cond_init() инициализирует сигнальную переменную. Первый ее аргумент — это указатель на объект типа pthread_cond_t. Второй аргумент (указатель на объект атрибутов сигнальной переменной) игнорируется в Linux. Исключающий семафор должен инициализироваться отдельно, как описывалось в разделе 4.4.2, "Исключающие семафоры".

■ Функция pthread_cond_signal() сигнализирует об изменении переменной. При этом разблокируется один из потоков, находящийся в ожидании сигнала. Если таких потоков нет, сигнал игнорируется. Аргументом функции является указатель на объект типа pthread_cond_t.

Похожая функция pthread_cond_broadcast() разблокирует все потоки, ожидающие данного сигнала.

■ Функция pthread_cond_wait() блокирует вызывающий ее поток до тех пор, пока не будет получен сигнал об изменении заданной переменной. Первым ее аргументом является указатель на объект типа pthread_cond_t. Второй аргумент — это указатель на объект исключающего семафора (тип pthread_mutex_t).

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

Перечисленные ниже этапы должны выполняться всякий раз, когда программа тем или иным способом меняет результат проверки условия, контролируемого сигнальной переменной (в нашей программе условие — это значение флага):

1. Захватить исключающий семафор, дополняющий сигнальную переменную.

2. Выполнить действие, включающее изменение результата проверки условия (в нашем случае — установить флаг).

3. Послать сигнал (возможно, широковещательный) об изменении условия.

4. Освободить исключающий семафор.

В листинге 4.14 показана измененная версия предыдущего примера, в которой на этот раз флаг защищается сигнальной переменной. Обратите внимание на то, что в функции thread_function() исключающий семафор захватывается до того, как будет проверено значение переменной thread_flag. Захват автоматически снимается функцией pthread_cond_wait() перед тем, как поток оказывается заблокированным, и также автоматически восстанавливается по завершении функции:

Листинг 4.14. (condvar.c) Управление работой потока с помощью сигнальной переменной

#include <pthread.h>

int thread_flag;

pthread_cond_t thread_flag_cv;

pthread_mutex_t thread_flag_mutex;

void initialize_flag() {

 /* Инициализация исключающего семафора и сигнальной

    переменной. */

 pthread_mutex_init(&thread_flag_mutex, NULL);

 pthread_cond_init(&thread_flag_cv, NULL);

 /* Инициализация флага. */

 thread_flag = 0;

}

/* Если флаг установлен, многократно вызывается функция

   do_work(). В противном случае поток блокируется. */

void* thread_function(void* thread_arg) {