
- •Раздел 3. Параллельное выполнение программ
- •3.1. Концепция процесса
- •3.2. Средства описания параллелизма
- •3.2.1. Графические средства
- •3.2.3. Описание процессов средствами uml
- •3.2.4. Языковые средства описания параллелизма
- •3.3. Организация ядра ос
- •3.3.1. Ядро как средство организации виртуальной машины
- •3.3.2. Состояния процесса и структура ядра
- •3.3.3. Дескрипторы процессов
- •3.3.4. Очереди процессов в ядре
- •3.4. Общая характеристика примитивов ядра
- •3.5. Примитивы создания и уничтожения процессов
- •3.6. Примитивы синхронизации процессов
- •3.6.1. Простейшие примитивы, не учтенные в классификации
- •3.6.2. Примитивы временной синхронизации
- •3.6.3. Примитивы событийной синхронизации процессов
- •3.7. Общий семафор как средство событийной синхронизации
- •3.8. Средства синхронизации в существующих операционных системах
- •3.9. Монитор как средство реализации взаимного исключения
- •3.10. Примитивы ядра для обмена сообщениями
- •3.10.1. Буфер как средство коммуникации между процессами
- •3.10.2. Почтовый ящик с очередью сообщений
- •3.10.3. Средства коммуникаций в существующих ос
- •3.11. Проблема тупиков при взаимодействии процессов
- •3.12. Планирование загрузки процессора в ядре
- •3.12.1. Классификация алгоритмов планирования
- •3.12.2. Тесты планируемости задач и классификация задач
- •3.12.3. Динамическое планирование
- •3.12.3.1. Планирование независимых задач
- •1. Алгоритм монотонной скорости.
- •2. Алгоритм “задача с минимальным предельным сроком завершения - первая”
- •3. Алгоритм минимальной неопределенности
- •3.12.3.2. Планирование зависимых задач
- •3.12.4. Статическое планирование
3.7. Общий семафор как средство событийной синхронизации
Семафор, который был рассмотрен в предыдущем разделе, называется двоичным семафором.
Вопрос (
Общий семафор содержит очередь, в которой процессы ждут освобождения ресурсов, а вместо флага занятости ресурса - счетчик. Содержимое счетчика позволяет оценить состояние семафора.
)Вопрос
Над семафором выполняются две операции, которые называются P- и V-операциями.
P-операция определяет условие блокировки процесса и может быть использована при входе в критический участок, V-операция определяет условие активизации процесса и может быть использована при выходе из критического участка.
Дадим формальное объектно-ориентированное описание семафора.
Type
PSemaphore = ^TSemaphore;
TSemaphore = Object
Count : Integer;
List : PList;
Constructor Init;
Destructor Done; Virtual;
Procedure P;
Procedure V;
End {TSemaphore};
Constructor TSemaphore.Init;
Begin
Count := 1;
List := New(PList, Init);
End {TSemaphore.Init};
Destructor TSemaphore.Done;
Begin
Dispose(List, Done);
End {TSemaphore.Done};
Вопрос (
Procedure TSemaphore.P;
Begin
Запрет_Прерываний;
Dec(Count);
If Count < 0 Then Begin
List^.Insert(Текущий_процесс);
ПЕРЕНАЗНАЧИТЬ_ПРОЦЕССОР;
End {If};
Разрешение_Прерываний;
End {TSemaphore.P};
)Вопрос
Вопрос (
Procedure TSemaphore.V;
Begin
Запрет_Прерываний;
Inc(Count);
If Count <= 0 Then Begin
List^.Remove(Первый_процесс);
Очередь_готовых^.Insert(Текущий_процесс);
ПЕРЕДАТЬ_УПРАВЛЕНИЕ(Текущий_процесс,Первый_процесс);
End {If};
Разрешение_Прерываний;
End {TSemaphore.V};
)Вопрос
Var
Semaphore : PSemaphore;
Begin
Semaphore := New(PSemaphore, Init);
...
Dispose(Semaphore, Done);
End.
Процесс:
Semaphore^.P;
Критический участок;
Semaphore^.V;
Замечание. Это Паскале-подобная запись, которая взята нами как средство описания. В литературе можно встретить и другие способы описания семафора, не меняющие сути дела.
Функционирование семафора
Вопрос (
Два класса задач могут быть решены с использованием семафора.
Безразлично, какой из процессов первым подойдет к критическому участку.
Примеры мы рассматривали. Все равно, какой процесс первым инкрементирует переменную N в примере предыдущей лекции.
Небезразлично, какой из процессов первым подойдет к критическому участку.
Пример с обменом данными через ячейку памяти.
Процесс 1: Процесс 2:
... ...
Запись в ЯП; Чтение из ЯП;
... ...
Ясно, что операцию чтения можно проводить только после операции записи.
)Вопрос
Рассмотрим сначала первую задачу.
Вопрос (
S := New(PSemaphore,Init);
Процесс 1 Процесс 2 Процесс 3
... ... ...
S^.P; S^.P; S^.P;
Кр. уч-к; Кр. уч-к; Кр. уч-к;
S^.V; S^.V; S^.V;
... ... ...
Dispose(S,Done);
Пусть П1 первым подошел к критическому участку. Count = 1.
Dec(Count); Count = 0;
Входит в критический участок, т. к. условие блокировки Count < 0.
Пусть П2 вторым подошел к критическому участку. Count = 0.
Dec(Count); Count = -1;
Встает в очередь семафора, т.к. выполняется условие блокировки.
Пусть П3 третьим подошел к критическому участку. Count = -1.
Dec(Count); Count = -2;
Встает в очередь семафора за П2, т. к. выполняется условие блокировки.
Теперь П1 выходит из критического участка. Count = -2.
Inc(Count); Count = -1;
Активизирует процесс П2, как первый в очереди, т. к. выполняется условие активизации
Count <= 0.
Процесс П2 входит в критический участок.
Теперь П2 выходит из критического участка. Count = -1.
Inc(Count); Count = 0;
Активизирует процесс П3, как первый в очереди, т. к. выполняется условие активизации
Count <= 0.
Процесс П3 входит в критический участок.
Теперь П3 выходит из критического участка. Count = 0.
Inc(Count); Count = 1;
Активизировать некого и не выполняется условие активизации Count <= 0.
Семафор приходит в исходное состояние.
Рассмотрим теперь вторую задачу.
Рассматривая вторую задачу, мы подбираемся к изучению методов обмена сообщениями в ядре.
Общая схема взаимодействия двух процессов при обмене данными выглядит следующим образом.
Процесс 1 Процесс 2
... ...
Записать в ЯП; Если не записано, то ЖДАТЬ;
Сообщить, что записал; Прочитать из ЯП;
... ...
Решим указанную задачу с помощью семафоров.
Инициализируем семафор S в 0, т.е. S^.Count := 0.
П1 П2
... ...
Записать в ЯП; S^.P;
S^.V; Прочитать из ЯП;
... ...
Пусть процесс П1 записал в ЯП данные и первым подошел к выполнению операции S^.V .
Inc(Count); Count = 1;
Т.к. условие активизации процессов в операции S^.V - Count <= 0, то активизировать некого.
Пусть теперь к выполнению операции S^.P подошел процесс П2.
Dec(Count); Count = 0;
Т.к. условие блокировки в операции S^.P - Сount < 0, то процесс П2 не блокируется и переходит к чтению ЯП.
Таким образом, чтение произошло после записи.
Пусть теперь первым подошел к выполнению операции S^.P процесс П2.
Dec(Count); Count = -1;
Выполняется условие блокировки и процесс П2 встает в очередь, не прочитав ячейку памяти.
Теперь процесс П1 пишет в ЯП и подходит к выполнению операции S^.V.
Inc(Count); Count = 0;
Т.к. условие активизации Count <= 0 выполняется, то происходит активизация процесса П2 и чтение ЯП.
Таким образом, чтение опять произошло после записи.
Для решения данной задачи в современных ОС используются семафоры событий.
Обычно процессы - это некие циклические программы. Поэтому, если организовать запись и чтение ЯП так, как это описано выше, то может иметь место следующая ситуация:
пока процесс П2 не подошел к операции чтения, процесс П1 может выполнить несколько операций записи, возвращаясь к ней в цикле. Т. е. может произойти потеря данных.
Поэтому, чтобы потери данных не происходили, необходимо организовать синхронизацию так, чтобы количество записей равнялось количеству чтений.
Следующая схема позволяет это сделать.
П1 П2
While True Do Begin While True Do Begin
... ...
Запись в ЯП; S^.P;
S^.V; Чтение из ЯП;
A^.P; A^.V;
... ...
End {While}; End {While};
Первый процесс пишет в ЯП, активизирует П2, если тот уже блокирован в очереди первого семафора, а сам блокируется в очереди второго семафора.
Активизация П1 выполняется процессом П2 после чтения ЯП. Первый семафор используется как подтверждение записи, а второй - как подтверждение чтения.
)Вопрос
Последний пример фактически является примером буфера на одну ячейку.
Замечания
Наличие очереди в семафоре - это, вообще говоря, вопрос реализации. Вместо очереди может быть организовано «активное ожидание».
При описании структуры семафора мы немного отступили от типовой структуры примитива ядра. Будем подходить к структуре примитива ядра гибко. Примитивы могут быть реализованы по-разному.
Вопрос (
Свойство семафора: для первой задачи имеет место свойство:
Если Count < 0, то |Сount| равен числу процессов, стоящих в очереди семафора.
)Вопрос