Главная страница  Межпроцессное взаимодействие (состязание) 

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 187

носит никакого вреда. Если используются несколько процессоров, каждый семафор необходимо защитить переменной блокировки посредством команды tsl, чтобы гарантировать одновременное обращение к семафору только одного процессора. Необходимо понимать, что использование команды tsl принципиально отличается от активного ожидания, при котором производитель или потребитель ждут наполнения или опустощения буфера. Операция с семафором займет несколько микросекунд, тогда как активное ожидание может затянуться на существенно больший промежуток времени.

Листинг 2.4. Решение проблемы производителя и потребителя с помощью семафоров

#define N 100 typedef int semaphore:

semaphore mutex = 1: semaphore empty = N: semaphore full =0:

void producerCvoid) {

int item:

while (TRUE) {

item = produce item(): down(&empty): down(&mutex): insert item(item): up(&mutex): up(&full):

/* Количество сегментов в буфере */ /* Семафоры - особый вид целочисленных /* переменных */

/* Контроль доступа в критическую область */ /* Число пустых сегментов буфера */ /* Число полных сегментов буфера */

/* TRUE - константа, равная 1*/

/* Создать данные, помещаемые в буфер */

/* Уменьшить счетчик пустых сегментов буфера */

/* Вход в критическую область */

/* Поместить в буфер новый элемент */

/* Выход из критической области */

/* Увеличить счетчик полных сегментов буфера */

void consumer(void)

int item: while (TRUE) {

down(&full):

down(&mutex):

item = removeJtemO:

up(&mutex):

up(&empty);

consume item(item):

/* Бесконечный цикл */

/* Уменьшить число полных сегментов буфера */

/* Вход в критическую область */

/* Удалить элемент из буфера */

/* Выход из критической области */

/* Увеличить счетчик пустых сегментов буфера */

/* Обработка элемента */

В представленном решении используются три семафора: один для подсчета заполненных сегментов буфера (full), другой для подсчета пустых сегментов (empty), а третий предназначен для исключения одновременного доступа производителя и потребителя (mutex) к буферу. Значение счетчика full исходно равно нулю, счетчик empty равен числу сегментов в буфере, а mutex равен 1. Семафоры, исходное значение которых установлено в 1, предназначенные для исключения одновременного нахождения в критической области двух процессов, называются двоичными семафорами. Взаимное исключение обеспечивается, если каждый



процесс выполняет операцию down перед входом в критическую область и up - после выхода из нее.

Теперь, когда у нас есть примитивы межпроцессного взаимодействия, вернемся к последовательности обработки прерываний, показанной в конце раздела 2.1.2. В системах, использующих семафоры, естественным способом скрыть прерывание будет связать с каждым устройством ввода/вывода семафор, изначально равный нулю. Сразу после включения устройства ввода/вывода управляющий процесс выполняет операцию down на соответствующем семафоре, тем самым входя в состояние блокировки. В случае прерывания обработчик прерывания выполняет up на соответствующем семафоре, переводя процесс в состояние готовности. В такой модели пятый шаг в алгоритме из раздела 2.1.2 заключается в выполнении up на семафоре устройства, чтобы следующим шагом планировщик смог запустить программу, управляющую устройством. Разумеется, если в этот момент несколько процессов находятся в состоянии готовности, планировщик вправе выбрать другой, более значимый процесс. Мы рассмотрим некоторые алгоритмы планирования позже в этой главе.

В примере, представленном в листинге 2.4, семафоры использовались двояко. Это различие достаточно ощутимо, чтобы сказать о нем особо. Семафор mutex предназначен для реализации взаимного исключения, то есть для исключения одновременного обращения к буферу и к связанным переменным двух процессов. Мы уже рассмотрели взаимное исключение и методы его реализации в предыдущем разделе.

Остальные семафоры введены с целью синхронизации. Семафоры full и empty необходимы, чтобы гарантировать, что определенные последовательности событий происходят или не происходят. В нашем случае они дают гарантию, что производитель прекращает работу, когда буфер полон, а потребитель прекращает ее, когда буфер пуст.

2.2.6. Мониторы

Межпроцессное взаимодействие с применением семафоров выглядит довольно просто, не правда ли? Эта простота кажущаяся. Взгляните внимательнее на порядок выполнения процедур down перед помещением или удалением элементов из буфера в листинге 2.4. Представьте себе, что две процедуры down в программе производителя поменялись местами, так что значение mutex было уменьшено раньше, чем empty. Если буфер был заполнен, производитель блокируется, сбросив mutex в 0. Соответственно, в следующий раз, когда потребитель обратится к буферу, он выполнит down с переменной mutex, равной О, и тоже заблокируется. Оба процесса заблокированы навсегда. Эта неприятная ситуация называется взаимоблокировкой, и мы вернемся к ней в главе 3.

Вышеизложенная ситуация показывает, с какой аккуратностью нужно обращаться с семафорами. Одна маленькая ошибка, и все останавливается. Это напоминает программирование на ассемблере, но на самом деле еще сложнее, поскольку такие ошибки приводят к абсолютно невоспроизводимым и непредсказуемым состояниям состязания, взаимоблокировкам и т. п.



Чтобы упростить написание программ, в 1974 году Хоар (Ноаге) [43] и Бринч Хансен (Brinch Hansen) предложили примитив синхронизации более высокого уровня, называемый монитором. Их предложения несколько отличались друг от друга, как мы увидим дальше. Монитор - набор процедур, переменных и других структур данных, объединенных в особый модуль или пакет. Процессы могут вызывать процедуры монитора, но у процедур, объявленных вне монитора, нет прямого доступа к внутренним структурам данных монитора. В листинге 2.5 представлен монитор, написанный на воображаемом языке, некоем местечковом диалекте - пиджин Pascal.

Листинг 2.5. Монитор

monitor example integer i: condition с:

procedure producerO:

end:

procedure consumerO:

end: end monitor:

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

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

Хотя мониторы предоставляют простой способ реализации взаимного исключения, этого недостаточно. Необходим также способ блокировки процессов, которые не могут продолжать свою деятельность. В случае проблемы производителя и потребителя достаточно просто поместить все проверки буфера на не-пустоту и пустоту в процедуры монитора, но как процесс заблокируется, обнаружив полный буфер?

Решение заключается в переменных состояния и двух операциях, wait и signal. Когда процедура монитора обнаруживает, что она не в состоянии продолжать



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 187

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