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);

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

ГЛАВА 7 Взаимные исключения и условные переменные

Из книги UNIX: взаимодействие процессов автора Стивенс Уильям Ричард

ГЛАВА 7 Взаимные исключения и условные переменные 7.1. Введение Эта глава начинается с обсуждения синхронизации — принципов синхронизации действий нескольких программных потоков или процессов. Обычно это требуется для предоставления нескольким потокам или процессам


7.4. Блокировка и ожидание

Из книги Программирование на языке Ruby [Идеология языка, теория и практика применения] автора Фултон Хэл

7.4. Блокировка и ожидание Продемонстрируем теперь, что взаимные исключения предназначены для блокирования, но не для ожидания. Изменим наш пример из предыдущего раздела таким образом, чтобы потребитель запускался сразу же после запуска всех производителей. Это даст


7.6. Условные переменные: время ожидания и широковещательная передача

Из книги HTML 5, CSS 3 и Web 2.0. Разработка современных Web-сайтов. автора Дронов Владимир

7.6. Условные переменные: время ожидания и широковещательная передача В обычной ситуации pthread_cond_signal запускает выполнение одного потока, ожидающего сигнал по соответствующей условной переменной. В некоторых случаях поток знает, что требуется пробудить несколько других


Функции и переменные. Локальные переменные

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

Функции и переменные. Локальные переменные Объявленные ранее функции создают внутри своего тела собственные переменные. Это так называемые локальные переменные. Такие переменные доступны только внутри тела функции, в котором они объявлены. При завершении выполнения


Функции и переменные. Локальные переменные

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

Функции и переменные. Локальные переменные Объявленные ранее функции создают внутри своего тела собственные переменные. Это так называемые локальные переменные. Такие переменные доступны только внутри тела функции, в котором они объявлены. При завершении выполнения


12.2.6. Ожидание сигналов

Из книги Firebird РУКОВОДСТВО РАЗРАБОТЧИКА БАЗ ДАННЫХ автора Борри Хелен

12.2.6. Ожидание сигналов Когда программа построена преимущественно вокруг сигналов, часто необходимо, чтобы она ожидала появления какого-то сигнала, прежде чем продолжать работу. Системный вызов pause() предоставляет простую возможность для этого.#include <unistd.h>int


17.3.4. Ожидание соединений

Из книги Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform автора Кёртен Роб

17.3.4. Ожидание соединений После создания сокета сервер привязывает к нему адрес с помощью функции bind(). Далее процесс сообщает системе путем вызова функции listen(), что он готов разрешить другим процессам соединение с данным сокетом (по указанному адресу). Если сокет привязан


17.4.2. Ожидание соединения

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

17.4.2. Ожидание соединения Как объяснялось выше, ожидание установки соединения на сокете домена Unix придерживается следующей процедуры: создание сокета, привязка адреса к сокету, перевод системы в режим ожидания соединений и принятие соединения.Ниже показан пример


17.5.7. Ожидание TCP-соединений

Из книги UNIX: разработка сетевых приложений автора Стивенс Уильям Ричард

17.5.7. Ожидание TCP-соединений Ожидание соединений TCP происходит почти идентично ожиданию соединений домена Unix. Единственные различия заключаются в семействах протоколов и адресов. Ниже показан вариант примера сервера домена Unix, который работает через сокеты TCP. 1: /* tserver.с


Ожидание условия

Из книги Разработка ядра Linux автора Лав Роберт

Ожидание условия Простое ожиданиеint pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);Вызов функции блокирует вызвавший поток на условной переменной cond и разблокирует мьютекс mutex. Поток блокируется до тех пор, пока другой поток не вызовет функцию разблокирования на условной


Асинхронная сигнализация

Из книги автора

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


Условные переменные

Из книги автора

Условные переменные Условные переменные (или «condvars») очень похожи на ждущие блокировки, которые мы рассматривали выше. В действительности, ждущие блокировки — это надстройка над механизмом условных переменных, и именно поэтому в таблице, иллюстрировавшей использование


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

Из книги автора

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


26.8. Условные переменные

Из книги автора

26.8. Условные переменные Взаимное исключение позволяет предотвратить одновременный доступ к совместно используемой (разделяемой) переменной, но для того чтобы перевести поток в состояние ожидания (спящее состояние) до момента выполнения некоторого условия, необходим


Условные переменные

Из книги автора

Условные переменные Условные переменные (conditional variable, completion variable) — простое средство синхронизации между двумя заданиями, которые работают в режиме ядра, когда необходимо, чтобы одно задание послало сигнал другому о том, что произошло некоторое событие. При этом одно