ИНФОРМАЦИЯ |
© Крюков В.А.
Курс лекций
"Распределенные
ОС"
Большой объем этой информации делает дорогими операции создания процессов, их переключение. Потребность в легковесных процессах, нитях (threads) возникла еще на однопроцессорных ЭВМ (физические процессы или их моделирование, совмещение обменов и счета), но для использования достоинств многопроцессорных ЭВМ с общей памятью они просто необходимы. Процессы могут быть независимыми, которые не требуют какой-либо синхронизации и обмена информацией (но могут конкурировать за ресурсы), либо взаимодействующими.
Если приложение реализовано в виде множества процессов (или нитей), то эти процессы (нити) могут взаимодействовать двумя основными способами:
При взаимодействии через общую память процессы должны синхронизовать свое выполнение. Различают два вида синхронизации - взаимное исключение критических интервалов и координация процессов. Критические секции. Недетерминизм, race condition (условия гонок).
Процесс p1 выполняет оператор I = I+J
, а процесс p2 - оператор
I = I-K
. Машинные коды выглядят так:
Процесс p1 | Процесс p2 |
---|---|
Load R1,I | Load R1,I |
Load R2,J | Load R2,J |
Add R1,R2 | Sub R1,R2 |
Store R1,I | Store R1,I |
Результат зависит от порядка выполнения этих команд. Требуется взаимное исключение критических интервалов. Решение проблемы взаимного исключения должно удовлетворять требованиям:
Взаимное исключение критических интервалов в многопроцессорной ЭВМ. Программные решения на основе неделимости операций записи и чтения из памяти.
int turn; boolean flag[2 ]; proc( int i ) { while (TRUE) { <вычисления>; enter_region( i ); <критический интервал>; leave_region( i ); } } void enter_region( int i ) { try: flag[i] = TRUE; while (flag [(i + 1) % 2]) { if ( turn == i ) continue; flag[ i ] = FALSE; while ( turn != i ); goto try; } } void leave_region( int i ) { turn = ( turn +1 ) % 2; flag[ i ] = FALSE; } turn = 0; flag[ 0 ] = FALSE; flag[ 1 ] = FALSE; proc( 0 ) AND proc( 1 ) /* запустили 2 процесса */
int turn; int flag[ 2 ]; void enter_region( int i ) { int other; /* номер другого процесса */ other = 1 - i; flag[ i ] = TRUE; turn = i; while (turn == i && flag[ other ] == TRUE) /* пустой оператор */; } void leave_region( int i ) { flag[ i ] = FALSE; }
Операция TSL(r,s): [r = s; s = 1]
Квадратные скобки - используются
для спецификации неделимости операций.
enter_region: tsl reg, flag cmp reg, #0 /* сравниваем с нулем */ jnz enter_region /* если не нуль - цикл ожидания */ ret leave_region: mov flag, #0 /* присваиваем нуль */ ret
Семафор - неотрицательная целая переменная, которая может изменяться и проверяться только посредством двух функций:
1. P - функция запроса семафора
P(s): [if (s == 0)
<заблокировать текущий процесс>; else s = s-1;]
2. V - функция освобождения семафора
V(s): [if (s
== 0) <разблокировать один из заблокированных процессов>; s = s+1;]
Двоичные семафоры как частный случай общих (считающих).
Использование семафоров для взаимного исключения критических интервалов и для
координации в задаче производитель-потребитель. Задача
производитель-потребитель (поставщик-потребитель, проблема ограниченного
буфера).
semaphore s = 1; semaphore full = 0; semaphore empty = N; |
|
producer() { int item; while (TRUE) { produce_item(&item); P(empty); P(s); enter_item(item); V(s); V(full); } } |
consumer() { int item; while (TRUE) { P(full); P(s); remove_item(&item); V(s); V(empty); consume_item(item); } } |
producer() AND consumer() /* запустили 2 процесса */ |
Реализация семафоров. Мультипрограммный режим.
Для многопроцессорной ЭВМ первые два способа не годятся. Для реализации третьего способа достаточно команды TSL и возможности объявлять прерывание указанному процессору. Блокирование процесса и переключение на другой - не эффективно, если семафор захватывается на очень короткое время. Ожидание освобождения таких семафоров может быть реализовано в ОС посредством циклического опроса значения семафора. Недостатки такого "активного ожидания" - бесполезная трата времени, нагрузка на общую память, и возможность фактически заблокировать работу процесса, находящегося в критическом нтервале
Если произведенный объект используется многими, то семафоры не годятся.
Это переменные, показывающие, что произошли определенные события. Для объявления события служит оператор POST(имя переменной), для ожидания события - WAIT (имя переменной). Для чистки (присваивания нулевого значения) - оператор CLEAR(имя переменной). Варианты реализации - не хранящие информацию (по оператору POST из ожидания выводятся только те процессы, которые уже выдали WAIT) , однократно объявляемые (нет оператора чистки).
Метод последовательной верхней релаксации (SOR) с использованием массива событий.
float A[ L1 ][ L2 ]; struct event s[ L1 ][ L2 ]; for ( i = 0; i < L1; i++) for ( j = 0; j < L2; j++) { clear( s[ i ][ j ]) }; for ( j = 0; j < L2; j++) { post( s[ 0 ][ j ]) }; for ( i = 0; i < L1; i++) { post( s[ i ][ 0 ]) }; .............. .............. parfor ( i = 1; i < L1-1; i++) parfor ( j = 1; j < L2-1; j++) { wait( s[ i-1 ][ j ]); wait( s[ i ][ j-1 ]); A[i][j] = (A[i-1][j] + A[i+1][j] + A[i][j-1] + A[i][j+1])/4; post( s[ i ][ j ]); }
Хоар, 1978 год, "Взаимодействующие параллельные процессы".
Цели - избавиться от проблем разделения памяти и предложить модель
взаимодействия процессов для распределенных
систем.
send(destination, &message,
msize);
receive([source], &message, msize);
Адресат - процесс. Отправитель - может не специфицироваться (любой). С буферизацией (почтовые ящики) или нет (рандеву - Ада, Оккам). Пайпы ОС UNIX - почтовые ящики, заменяют файлы и не хранят границы сообщений (все сообщения объединяются в одно большое, которое можно читать произвольными порциями.
Пример использования буферизуемых сообщений.
#define N 100 /* максимальное число сообщений в буфере*/ #define msize 4 /* размер сообщения*/ typedef int message[msize]; producer() { message m; int item; while (TRUE) { produce_item(&item); receive(consumer, &m, msize); /* получает пустой "контейнер" */ build_message(&m, item); /* формирует сообщение */ send(consumer, &m, msize); } } consumer() { message m; int item, i; for (i = 0; i < N; i ++) send (producer, &m, msize); /* посылает все пустые "контейнеры" */ while (TRUE) { receive(producer, &m, msize); extract_item(&m, item); send(producer, &m, msize); /* возвращает "контейнер" */ consume_item(item); }; } producer() AND consumer() /* запустили 2 процесса */
Механизмы семафоров и обмена сообщениями взаимозаменяемы семантически и на мультипроцессорах могут быть реализованы один через другой. Другие классические задачи взаимодействия процессов - проблема обедающих философов (Дейкстра) и "читатели-писатели".
Планирование процессоров очень сильно влияет на производительность мультипроцессорной системы. Можно выделить следующие главные причины деградации производительности:
Применяются следующие стратегии борьбы с деградацией производительности.
Содержание курса | Далее: Лекция 3. |
© Лаборатория Параллельных Информационных
Технологий, НИВЦ МГУ