разблокирует все потоки, ожидающие данного сигнала.
■ Функция
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) {
/* Бесконечный цикл. */
while (1) {
/* Захватываем исключающий семафор, прежде чем обращаться
к флагу. */
pthread_mutex_lock(&thread_flag_mutex);
while (!thread_flag)
/* Флаг сброшен. Ожидаем сигнала об изменении условной
переменной, указывающего на то, что флаг установлен.
При поступлении сигнала поток разблокируется и снова
/* При выходе из цикла освобождаем исключающий семафор. */
pthread_mutex_unlock(&thread_flag_mutex);
/* Выполняем требуемые действия. */
do_work;
}
return NULL;
}
/* Задаем значение флага равным FLAG_VALUE. */
void set_thread_flag(int flag_value) {
/* Захватываем исключающий семафор, прежде чем изменять
значение флага. */
pthread_mutex_lock(&thread_flag_mutex);
/* Устанавливаем флаг и посылаем сигнал функции
thread_function, заблокированной в ожидании флага.
Правда, функция не сможет проверить флаг, пока
исключающий семафор не будет освобожден. */
thread_flag = flag_value;
pthread_cond_signal(&thread_flag_cv);
/* освобождаем исключающий семафор. */
pthread_mutex_unlock(&thread_flag_mutex);
}
Условие, контролируемое сигнальной переменной, может быть произвольно сложным. Но перед выполнением любой операции, способной повлиять на результат проверки условия, необходимо захватить исключающий семафор, и только после этого можно посылать сигнал.
Сигнальная переменная может вообще не быть связана ни с каким условием, а служить лишь средством блокирования потока до тех пор, пока какой-нибудь другой поток не "разбудит" его. Для этой же цели может использоваться и семафор. Принципиальная разница между ними заключается в том, что семафор "запоминает" сигнал, даже если ни один поток в это время не был заблокирован, а сигнальная переменная регистрирует сигнал только в том случае, если его ожидает какой-то поток. Кроме того, семафор всегда разблокирует лишь один поток, тогда как с помощью функции
pthread_cond_broadcast
можно разблокировать произвольное число потоков.
4.4.7. Взаимоблокировки двух и более потоков
Взаимоблокировка происходит, когда два (или более) потока блокируются в ожидании события, наступление которого на самом деле зависит от действий одного из заблокированных потоков. Например, если поток A ожидает изменения сигнальной переменной, устанавливаемой в потоке Б, а поток Б, в свою очередь, ждет сигнала от потока А, возникает тупиковая ситуация. Ни один из потоков никогда не пошлет сигнал другому. Необходимо тщательно избегать таких ситуаций, потому что их очень трудно обнаруживать.