Книга: Программирование для Linux. Профессиональный подход
Листинг 4.15. (spin-condvar.c) Простейшая реализация сигнальной переменной
Листинг 4.15. (spin-condvar.c) Простейшая реализация сигнальной переменной
#include <pthread.h>
int thread_flag;
pthread_mutex_t thread_flag_mutex;
void initialize_flag() {
pthread_mutex_init(&thread_flag_mutex, NULL);
thread_flag = 0;
}
/* Если флаг установлен, многократно вызывается функция do_work().
В противном случае цикл работает вхолостую. */
void* thread_function(void* thread_arg) {
while (1) {
int flag_is_set;
/* Защищаем флаг с помощью исключающего семафора. */
pthread_mutex_lock(&thread_flag_mutex);
flag_is_set = thread_flag;
pthread_mutex_unlock(&thread_flag_mutex);
if (flag_is_set)
do_work();
/* Если флаг не установлен, ничего не делаем. Просто переходим
на следующую итерацию цикла. */
}
return NULL;
}
/* Задаем значение флага равным FLAG_VALUE. */
void set_thread_flag(int flag_value) {
/* Защищаем флаг с помощью исключающего семафора. */
pthread_mutex_lock(&thread_flag_mutex);
thread_flag = flag_value;
pthread_mutex_unlock(&thread_flag_mutex);
}
Сигнальная переменная позволяет организовать такую проверку, при которой поток либо выполняется, либо блокируется. Как и в случае семафора, поток может ожидать сигнальную переменную. Поток A, находящийся в режиме ожидания, блокируется до тех пор, пока другой поток. Б, не просигнализирует об изменении состояния этой переменной. Сигнальная переменная не имеет внутреннего счетчика, что отличает ее от семафора. Поток А должен перейти в состояние ожидания до того, как поток Б пошлет сигнал. Если сигнал будет послал раньше, он окажется потерянным и поток А заблокируется, пока какой-нибудь поток не пошлет сигнал еще раз.
Вот как можно сделать предыдущую программу более эффективной.
? Функция thread_function()
в цикле проверяет флаг. Если он не установлен, поток переходит в режим ожидания сигнальной переменной.
? Функция set_thread_flag()
устанавливает флаг и сигнализирует об изменении условной переменной. Если функция thread_function()
была заблокирована в ожидании сигнала, она разблокируется и снова проверяет флаг.
Но существует одна проблема: возникает гонка между операцией проверки флага и операцией сигнализирования или ожидания сигнала. Предположим, что функция thread_function()
проверяет флаг и обнаруживает, что он не установлен. В этот момент планировщик Linux прерывает выполнение данного потока и активизирует главную программу. По стечению обстоятельств программа как раз находится в функции set_thread_flag()
. Она устанавливает флаг и сигнализирует об изменении условной переменной. Но поскольку в данный момент нет потока, ожидающего получения этого сигнала (вспомните, что функция thread_function()
была прервана перед тем, как перейти в режим ожидания), сигнал окажется потерян. Когда Linux вновь активизирует дочерний поток, он начнет ждать сигнал, который, возможно, никогда больше не придет.
Чтобы избежать этой проблемы, необходимо одновременно захватить и флаг, и сигнальную переменную с помощью исключающего семафора. К счастью, в 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()
перед тем, как поток оказывается заблокированным, и также автоматически восстанавливается по завершении функции:
- 9.4.1. Реализация графа в виде матрицы смежности
- Листинг 10.1. (simpleid.c) Отображение идентификаторов пользователя и группы
- Использование переменной окружения ISC_PATH
- Реализация языка SQL
- LOCK ACQUIRE SPINS
- Листинг 15.11. Код для загрузки файла с Web-сервера
- 9.2.1. Более строгая реализация стека
- 9.2 Реализация массива ftAID на платформе Windows NT
- Листинг 3.1. Конфигурационный файл lilo.conf
- Листинг 3.2. Файл настройки программы inittab
- Листинг 3.3. Файл
- Листинг 3.4. Информация о конфигурации и состоянии сети