Чтение онлайн

ЖАНРЫ

Программирование для Linux. Профессиональный подход

Самьюэл Алекс

Шрифт:

В листинге 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_cond_wait(&thread_flag_cv, &thread_flag_mutex);

/* При выходе из цикла освобождаем исключающий семафор. */

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 ожидает изменения сигнальной переменной, устанавливаемой в потоке Б, а поток Б, в свою очередь, ждет сигнала от потока А, возникает тупиковая ситуация. Ни один из потоков никогда не пошлет сигнал другому. Необходимо тщательно избегать таких ситуаций, потому что их очень трудно обнаруживать.

Чаще всего взаимоблокировка возникает, когда группа потоков пытается захватить один и тот же набор объектов. Рассмотрим, к примеру, программу, в которой два потока, выполняющих разные потоковые функции, должны захватить одни и те же два исключающих семафора. Предположим, ноток А захватывает сначала семафор 1, а затем семафор 2, в то время как поток Б захватывает семафоры в обратном порядке. Возможна достаточно неприятная ситуация, когда после захвата семафора 1 потоком А операционная система активизирует поток Б, который захватит поток 2. Далее оба потока окажутся заблокированными, так как им будет закрыт доступ к семафорам друг друга.

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

4.5. Реализация потоков в Linux

Потоковые функции, соответствующие стандарту POSIX, реализованы в Linux не так, как в большинстве других версий UNIX. Суть в том, что в Linux потоки реализованы в виде процессов. Когда вызывается функция

pthread_create
, операционная система на самом деле создает новый процесс, выполняющий поток. Но это не тот процесс, который создается функцией
fork
. Он, в частности, делит общее адресное пространство и ресурсы с исходным процессом, а не получает их копии.

Сказанное иллюстрирует программа

thread-pid
, показанная в листинге 4.15. Она отображает идентификатор главного потока с помощью функции
getpid
и создает новый поток, в котором тоже выводится значение идентификатора, после чего оба потока входят в бесконечный цикл.

Поделиться с друзьями: