7.5. Условные переменные: ожидание и сигнализация
7.5. Условные переменные: ожидание и сигнализация
Взаимное исключение используется для блокирования, а условная переменная — для ожидания. Это два различных средства синхронизации, и оба они нужны. Условная переменная представляет собой переменную типа pthread_cond_t. Для работы с такими переменными предназначены две функции:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cptr, pthread_m_tex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
/* Обе функции возвращают 0 в случае успешного завершения, положительное значение Еххх – в случае ошибки */
Слово signal в имени второй функции не имеет никакого отношения к сигналам Unix SIGxxx.
Мы определяем условие, уведомления о выполнении которого будем ожидать.
Взаимное исключение всегда связывается с условной переменной. При вызове pthread_cond_wait для ожидания выполнения какого-либо условия мы указываем адрес условной переменной и адрес связанного с ней взаимного исключения.
Мы проиллюстрируем использование условных переменных, переписав пример из предыдущего раздела. В листинге 7.5 объявляются глобальные переменные.
Переменные производителя и взаимное исключение объединяются в структуру
7-13 Две переменные nput и rival ассоциируются с mutex, и мы объединяем их в структуру с именем put. Эта структура используется производителями.
14-20 Другая структура, nready, содержит счетчик, условную переменную и взаимное исключение. Мы инициализируем условную переменную с помощью PTHREAD_ COND_INITIALIZER.
Функция main по сравнению с листингом 7.3 не изменяется.
Листинг 7.5. Глобальные переменные: использование условной переменной
//mutex/prodcons6.c
1 #include "unpipc.h"
2 #define MAXNITEMS 1000000
3 #define MAXNTHREADS 100
4 /* глобальные переменные для всех потоков */
5 int nitems; /* только для чтения потребителем и производителем */
6 int buff[MAXNITEMS];
7 struct {
8 pthread_mutex_t mutex;
9 int nput; /* следующий сохраняемый элемент */
10 int nval; /* следующее сохраняемое значение */
11 } put = {
12 PTHREAD_MUTEX_INITIALIZER
13 };
14 struct {
15 pthread_mutex_t mutex:
16 pthread_cond_t cond;
17 int nready; /* количество готовых для потребителя */
18 } nready = {
19 PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER
20 };
Функции produce и consume претерпевают некоторые изменения. Их текст дан в листинге 7.6.
Листинг 7.6. Функции produce и consume
//mutex/prodcons6.c
46 void *
47 produce(void *arg)
48 {
49 for (;;) {
50 Pthread_mutex_lock(&put.mutex);
51 if (put.nput >= nitems) {
52 Pthread_mutex_unlock(&put.mutex);
53 return(NULL); /* массив заполнен, готово */
54 }
55 buff[put.nput] = put.nval;
56 put.nput++;
57 put.nval++;
58 Pthread_mutex_unlock(&put.mutex);
59 Pthread_mutex_lock(&nready.mutex):
60 if (nready.nready == 0)
61 Pthread_cond_signal(&nready.cond);
62 nready.nready++;
63 Pthread_mutex_unlock(&nready.mutex);
64 *((int *) arg) += 1;
65 }
66 }
67 void*
68 consume(void *arg)
69 {
70 int i;
71 for (i = 0; i < nitems; i++) {
72 Pthread_mutex_lock(&nready.mutex);
73 while (nready.nready == 0)
74 Pthread_cond_wait(&nready.cond, &nready.mutex);
75 nready.nready--;
76 Pthread_mutex_unlock(&nready.mutex);
77 if (buff[i] != i)
78 printf("buff[%d] = *d ", i, buff[i]);
79 }
80 return(NULL);
81 }
Помещение очередного элемента в массив
50-58 Для блокирования критической области в потоке-производителе теперь используется исключение put.mutex.
Уведомление потребителя
59-64 Мы увеличиваем счетчик nready.nready, в котором хранится количество элементов, готовых для обработки потребителем. Перед его увеличением мы проверяем, не было ли значение счетчика нулевым, и если да, то вызывается функция pthread_cond_signal, позволяющая возобновить выполнение всех потоков (в данном случае потребителя), ожидающих установки ненулевого значения этой переменной. Теперь мы видим, как взаимодействуют взаимное исключение и связанная с ним условная переменная. Счетчик используется совместно потребителем и производителями, поэтому доступ к нему осуществляется с блокировкой соответствующего взаимного исключения (nready.mutex). Условная переменная используется для ожидания и передачи сигнала.
Потребитель ждет, пока значение nready.nready не станет отличным от нуля
72-76 Потребитель просто ждет, пока значение счетчика nready. nready не станет отличным от нуля. Поскольку этот счетчик используется совместно с производителями, его значение можно проверять только при блокировке соответствующего взаимного исключения. Если при проверке значение оказывается нулевым, мы вызываем pthread_cond_wait для приостановки процесса. При этом выполняются два атомарных действия:
1. Разблокируется nready.mutex.
2. Выполнение потока приостанавливается, пока какой-нибудь другой поток не вызовет pthread_cond_signal для этой условной переменной.
Перед возвращением управления потоку функция pthread_cond_wait блокирует nready.mutex. Таким образом, если после возвращения из функции мы обнаруживаем, что счетчик имеет ненулевое значение, мы уменьшаем этот счетчик (зная, что взаимное исключение заблокировано) и разблокируем взаимное исключение. Обратите внимание, что после возвращения из pthread_cond_wait мы всегда заново проверяем условие, поскольку может произойти ложное пробуждение при отсутствии выполнения условия. Различные реализации стремятся уменьшить количество ложных пробуждений, но они все равно происходят.
Код, передающий сигнал условной переменной, выглядит следующим образом:
struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
переменные, для которых устанавливается условие
} var = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, … };
Pthread_mutex_lock(&var.mutex);
установка истинного значения условия
Pthread_cond_signal(&var.cond);
Pthread_mutex_unlock(&var.mutex);
В нашем примере переменная, для которой устанавливалось условие, представляла собой целочисленный счетчик, а установка условия означала просто увеличение счетчика. Мы оптимизировали программу, посылая сигнал только при изменении значения счетчика с 0 на 1.
Код, проверяющий условие и приостанавливающий процесс, если оно не выполняется, обычно выглядит следующим образом:
Pthread_mutex_lock(&var.mutex);
while (условие ложно)
Pthread_cond_wait(&var.cond, &var.mutex);
изменение условия
Pthread_mutex_unlock(&var.mutex);
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
Условные переменные
Условные переменные Условные переменные (conditional variable, completion variable) — простое средство синхронизации между двумя заданиями, которые работают в режиме ядра, когда необходимо, чтобы одно задание послало сигнал другому о том, что произошло некоторое событие. При этом одно
Условные переменные
Условные переменные Условные переменные (или «condvars») очень похожи на ждущие блокировки, которые мы рассматривали выше. В действительности, ждущие блокировки — это надстройка над механизмом условных переменных, и именно поэтому в таблице, иллюстрировавшей использование
Ожидание условия
Ожидание условия Простое ожиданиеint pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);Вызов функции блокирует вызвавший поток на условной переменной cond и разблокирует мьютекс mutex. Поток блокируется до тех пор, пока другой поток не вызовет функцию разблокирования на условной
12.2.6. Ожидание сигналов
12.2.6. Ожидание сигналов Когда программа построена преимущественно вокруг сигналов, часто необходимо, чтобы она ожидала появления какого-то сигнала, прежде чем продолжать работу. Системный вызов pause() предоставляет простую возможность для этого.#include <unistd.h>int
17.3.4. Ожидание соединений
17.3.4. Ожидание соединений После создания сокета сервер привязывает к нему адрес с помощью функции bind(). Далее процесс сообщает системе путем вызова функции listen(), что он готов разрешить другим процессам соединение с данным сокетом (по указанному адресу). Если сокет привязан
17.4.2. Ожидание соединения
17.4.2. Ожидание соединения Как объяснялось выше, ожидание установки соединения на сокете домена Unix придерживается следующей процедуры: создание сокета, привязка адреса к сокету, перевод системы в режим ожидания соединений и принятие соединения.Ниже показан пример
17.5.7. Ожидание TCP-соединений
17.5.7. Ожидание TCP-соединений Ожидание соединений TCP происходит почти идентично ожиданию соединений домена Unix. Единственные различия заключаются в семействах протоколов и адресов. Ниже показан вариант примера сервера домена Unix, который работает через сокеты TCP. 1: /* tserver.с
Функции и переменные. Локальные переменные
Функции и переменные. Локальные переменные Объявленные ранее функции создают внутри своего тела собственные переменные. Это так называемые локальные переменные. Такие переменные доступны только внутри тела функции, в котором они объявлены. При завершении выполнения
26.8. Условные переменные
26.8. Условные переменные Взаимное исключение позволяет предотвратить одновременный доступ к совместно используемой (разделяемой) переменной, но для того чтобы перевести поток в состояние ожидания (спящее состояние) до момента выполнения некоторого условия, необходим
Функции и переменные. Локальные переменные
Функции и переменные. Локальные переменные Объявленные ранее функции создают внутри своего тела собственные переменные. Это так называемые локальные переменные. Такие переменные доступны только внутри тела функции, в котором они объявлены. При завершении выполнения
ГЛАВА 7 Взаимные исключения и условные переменные
ГЛАВА 7 Взаимные исключения и условные переменные 7.1. Введение Эта глава начинается с обсуждения синхронизации — принципов синхронизации действий нескольких программных потоков или процессов. Обычно это требуется для предоставления нескольким потокам или процессам
7.4. Блокировка и ожидание
7.4. Блокировка и ожидание Продемонстрируем теперь, что взаимные исключения предназначены для блокирования, но не для ожидания. Изменим наш пример из предыдущего раздела таким образом, чтобы потребитель запускался сразу же после запуска всех производителей. Это даст
7.6. Условные переменные: время ожидания и широковещательная передача
7.6. Условные переменные: время ожидания и широковещательная передача В обычной ситуации pthread_cond_signal запускает выполнение одного потока, ожидающего сигнал по соответствующей условной переменной. В некоторых случаях поток знает, что требуется пробудить несколько других
4.4.6. Сигнальные (условные) переменные
4.4.6. Сигнальные (условные) переменные Мы узнали, как с помощью исключающего семафора защитить переменную от одновременного доступа со стороны двух и более потоков и как посредством обычного семафора реализовать счетчик обращений, доступный нескольким потокам.
Асинхронная сигнализация
Асинхронная сигнализация Асинхронная сигнализация имеет свои ограничения. В частности, она требует, чтобы приложение ожидало оповещения бесконечное время. Это ограничение модели было устранено при поддержке асинхронной сигнализации.В этой модели приложение также