Читать «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). Условная переменная используется для ожидания и передачи сигнала.