Главная страница  Взаимодействие нетривиальных процессов 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 [ 57 ] 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

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

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

В листинге 7.3 приведен текст функции main. Начало кода (до объявления функции main) не претерпело никаких изменений по сравнению с листингом 7.1.

Листинг 7.3. Функция main: запуск потребителя сразу после запуска производителей

mutex/prodconsS.c

14 int

15 niain(int argc. char **argv)

16 {

17 int i. nthreads. count[MAXNTHREAOS]:

18 pthread t tid produce[MAXNTHREADS]. tid consunie:

19 if (argc !- 3)

20 err quit( usage: prodcons3 <#itenis> <#threads> ):

21 nitems = min(atoi(argv[l]). MAXNITEMS):

22 nthreads = min(atoi(argv[2]). MAXNTHREADS):

23 /* создание всех производителей и одного потребителя */

24 Set concurrency(nthreads + 1):

25 for (i = 0: i < nthreads: i++) {

26 countEi] = 0:

27 Pthread create(&tid produce[i]. NULL, produce. &count[i]);

28 }

29 Pthread create(&tid consume. NULL, consume. NULL):

30 /* ожидание завершения производителей и потребителя */

31 for (i = 0; i < nthreads; i++) {

32 PthreadJoin(tid produce[i]. NULL);

33 printf( count[d] = d\n . i. count[i]);

34 }

35 PthreadJoin(tid consume. NULL);

36 exit(O);

37 }

24 Мы увеличиваем уровень параллельного выполнения на единицу, чтобы учесть поток-потребитель, выполняемый параллельно с производителями.

25-29 Поток-потребитель создается сразу же после создания потоков-производителей.

Функция produce по сравнению с листингом 7.2 не изменяется, в листинге 7.4 приведен текст функции consume, вызывающей новую функцию consume wait.



Листинг 7.4. Функции consume и consume wait

mutex/prodconsS.c

54 void

55 consunie wait(int i)

56 {

57 for ( : ; ) {

58 Pthread niutex lock(&shared.mutex);

59 if (i < shared.nput) {

60 Pthread niutex unlock(&shared.mutex);

61 return: /* элемент готов */

62 }

63 Pthread mutex unlock(&shared.mutex);

64 }

65 }

66 void *

67 consume(void *arg)

68 {

69 int i;

70 for (i = 0; i < nitems; i++) {

71 consume wait(i);

72 if (shared.buffEi] != i)

73 printf( buff[M] - M\n . i. Shared.buffEi]);

74 }

75 return(NULL);

76 }

Потребитель должен ждать

71 Единственное изменение в функции consume заключается в добавлении вызова consume wait перед обработкой следующего элемента массива.

Ожидание производителей

57-64 Наша функция consume wait должна ждать, пока производители не создадут i -й элемент. Для проверки этого условия производится блокировка взаимного исключения и значение i сравнивается с индексом производителя nput. Блокировка необходима, поскольку значение nput может быть изменено одним из производителей в момент его проверки.

Главная проблема - что делать, если нужный элемент еще не готов. Все, что нам остается и что мы делаем в листинге 7.4, - это повторять операции в цикле, устанавливая и снимая блокировку и проверяя значение индекса. Это называется опросом (spinning или polling) и является лишней тратой времени процессора.

Мы могли бы приостановить выполнение процесса на некоторое время, но мы не знаем, на какое. Что нам действительно нужно - это использовать какое-то другое средство синхронизации, позволяющее потоку или процессу приостанавливать работу, пока не произойдет какое-либо событие.

7.5. Условные переменные: ожидание и сигнализация

Взаимное исключение используется для блокирования, а условная переменная - для ожидания. Это два различных средства синхронизации, и оба они нужны. Условная переменная представляет собой переменную типа



finclude unpipc.h

fdefine MAXNITEMS 1000000

fdefine MAXNTHREADS

/* глобальные переменные для всех потоков */

int nitems:

/* только для чтения потребителем и

производителем */

int buff[MAXNITEMS];

struct {

pthread mutex t mutex;

int nput:

/* следующий сохраняемый элемент */

int nval:

/* следующее сохраняемое значение */

} put = {

PTHREAD MUTEX INITIALIZER

struct {

pthread mutex t mutex;

pthread cond t cond;

int nready;

/* количество готовых для потребителя */

pthread cond t. Для работы с такими переменными предназначены две функции:

finclude <pthread.h>

int pthread cond wait(pthread cond t *cptr. pthread niutex t *mptr)\ int pthread cond signal(pthread cond t *cptr)\

/* Обе функции возвращают О в случае успешного завершения, положительное значение Еххх - в случае ошибки */

Слово signal в имени второй функции не имеет никакого отношения к сигналам Unix SIGxxx.

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

Взаимное исключение всегда связывается с условной переменной. При вызове pthread cond wait для ожидания выполнения какого-либо условия мы указываем адрес условной переменной и адрес связанного с ней взаимного исключения.

Мы проиллюстрируем использование условных переменных, переписав пример из предыдущего раздела. В листинге 7.5 объявляются глобальные переменные.

Переменные производителя и взаимное исключение объединяются в структуру

7-13 Две переменные nput и nval ассоциируются с mutex, и мы объединяем их в структуру с именем put. Эта структура используется производителями.

14-20 Другая структура, nready, содержит счетчик, условную переменную и взаимное исключение. Мы инициализируем условную переменную с помощью PTHREAD COND INITIALIZER.

Функция main по сравнению с листингом 7.3 не изменяется.

Листинг 7.5. Глобальные переменные: использование условной переменной

mutex/prodconse.c



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 [ 57 ] 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

© 2000 - 2024 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования.