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

на главную - закладки

Жанры

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

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

Шрифт:

4.4.6. Сигнальные (условные) переменные

Мы узнали, как с помощью исключающего семафора защитить переменную от одновременного доступа со стороны двух и более потоков и как посредством обычного семафора реализовать счетчик обращений, доступный нескольким потокам. Сигнальная переменная (называемая также условной переменной) — это третий элемент синхронизации в Linux. Благодаря ему можно задавать более сложные условия выполнения потоков.

Предположим, требуется написать потоковую функцию, которая входит в бесконечный цикл, выполняя на каждой итерации какие-то действия. Но работа цикла должна контролироваться флагом: действие выполняется только в том случае, когда он установлен.

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

Листинг 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
.

Поделиться:
Популярные книги

Товарищ "Чума"

lanpirot
1. Товарищ "Чума"
Фантастика:
попаданцы
альтернативная история
4.00
рейтинг книги
Товарищ Чума

Матабар. II

Клеванский Кирилл Сергеевич
2. Матабар
Фантастика:
фэнтези
5.00
рейтинг книги
Матабар. II

Вернувшийся: Первые шаги. Том II

Vector
2. Вернувшийся
Фантастика:
боевая фантастика
космическая фантастика
рпг
5.00
рейтинг книги
Вернувшийся: Первые шаги. Том II

Печать Пожирателя 3

Соломенный Илья
3. Пожиратель
Фантастика:
городское фэнтези
аниме
сказочная фантастика
фэнтези
попаданцы
5.00
рейтинг книги
Печать Пожирателя 3

Кодекс Охотника. Книга X

Винокуров Юрий
10. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
6.25
рейтинг книги
Кодекс Охотника. Книга X

Черный Маг Императора 7 (CИ)

Герда Александр
7. Черный маг императора
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Черный Маг Императора 7 (CИ)

Мальвиль

Мерль Робер
Фантастика:
социально-философская фантастика
научная фантастика
альтернативная история
8.29
рейтинг книги
Мальвиль

Эволюционер из трущоб. Том 10

Панарин Антон
10. Эволюционер из трущоб
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Эволюционер из трущоб. Том 10

Бастард Императора. Том 13

Орлов Андрей Юрьевич
13. Бастард Императора
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Бастард Императора. Том 13

Александр Агренев. Трилогия

Кулаков Алексей Иванович
Александр Агренев
Фантастика:
альтернативная история
9.17
рейтинг книги
Александр Агренев. Трилогия

Вернувшийся: Корпорация. Том III

Vector
3. Вернувшийся
Фантастика:
космическая фантастика
боевая фантастика
рпг
5.00
рейтинг книги
Вернувшийся: Корпорация. Том III

Ваше Сиятельство 8

Моури Эрли
8. Ваше Сиятельство
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Ваше Сиятельство 8

Изгой Проклятого Клана. Том 6

Пламенев Владимир
6. Изгой
Фантастика:
аниме
фэнтези
фантастика: прочее
попаданцы
5.00
рейтинг книги
Изгой Проклятого Клана. Том 6

Сильнейший ученик. Том 2

Ткачев Андрей Юрьевич
2. Пробуждение крови
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Сильнейший ученик. Том 2