Читать «UNIX: взаимодействие процессов» онлайн - страница 116

Уильям Ричард Стивенс

Мы определяем условие, уведомления о выполнении которого будем ожидать.

Взаимное исключение всегда связывается с условной переменной. При вызове 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\n", i, buff[i]);

79  }

80  return(NULL);

81 }

Помещение очередного элемента в массив

50-58 Для блокирования критической области в потоке-производителе теперь используется исключение put.mutex. 

Уведомление потребителя

59-64 Мы увеличиваем счетчик nready.nready, в котором хранится количество элементов, готовых для обработки потребителем. Перед его увеличением мы проверяем, не было ли значение счетчика нулевым, и если да, то вызывается функция pthread_cond_signal, позволяющая возобновить выполнение всех потоков (в данном случае потребителя), ожидающих установки ненулевого значения этой переменной. Теперь мы видим, как взаимодействуют взаимное исключение и связанная с ним условная переменная. Счетчик используется совместно потребителем и производителями, поэтому доступ к нему осуществляется с блокировкой соответствующего взаимного исключения (nready.mutex). Условная переменная используется для ожидания и передачи сигнала.