Синхронизация процессов
С взаимодействием процессов тесно связано понятие синхронизации. Синхронизация необходима для корректного разделения общих ресурсов, обмена данными между процессами и т.п.. Сразу же заметим, что для синхронизации процессов должен быть создан набор непрерываемых (целостных) примитивов (элементарных действий), выполнение которых одним процессом не должно прерываться аналогичными действиями других процессов. Такие фрагменты программного кода называются еще " критическими секциями" . В нашей программе мы уже сталкивались с простейшим способом ограничения таких переключений - установка в 1 переменной RUN любым процессом приводит к запрещению его переключения диспетчером, поэтому любой процесс ограждает свою критическую секцию операциями RUN++ ... RUN
Механизмы синхронизации могут быть разными. Широко используются " почтовые ящики" - сообщений с неделимыми операциями " послать" , " проверить" и " получить" сообщение. Другим механизмом, который в силу своей простоты является наиболее универсальным и поэтому используется для проектирования других систем синхронизации, является семафор.
Семафор имеет некоторое начальное значение и две операции P и V. Операция P уменьшает на 1 значение семафора, если он не равен 0. Иначе процесс ожидает (с блокировкой или без нее), пока значение семафора не станет отличным от 0. V - операция увеличивает на 1 значение семафора. Операции проверки и изменения значения переменной являются неделимыми. В нашей модели эти операции (с циклическим ожиданием) выглядят следующим образом (Заметим, что явное переключение процессов - в данном случае вещь чисто техническая и не принципиальная, предназначенная для более быстрой реакции нужных процессов по произошедшим событиям).
void P(int *sem)
{while(1)
{ RUN++; // " Критическая секция"
if (*sem!=0) // Уменьшить ненулевой семафор и выйти
{ (*sem)--; RUN--; return; }
else // Повторить (с переключение процессов)
{ RUN--; NOCLOCK++;
geninterrupt(TIMVEC);
}
}}
void V(int *sem) // Увеличить семафор
{ RUN++; (*sem)++; RUN--; // Выйти (с переключением процессов)
NOCLOCK++; geninterrupt(TIMVEC);
}
В заключение приведем пример решения классической задачи " поставщик - потребитель" , взаимодействующих через общий циклический буфер .
#define N 5 // Размер буфера
#define TW 18*2 // Время " работы" потребителя
int EMPTY=N, // Семафор " БУФЕР ПОЛОН"
FULL=0, // Семафор " БУФЕР ПУСТ"
SEMBUF=1; // Семафор " ОПЕРАЦИЯ С БУФЕРОМ"
int fst=0,lst=0; // Циклическая очередь
char BUFF[N];
void consume() // Процесс - потребитель
{ while(1)
{ WAIT(TW); // Процесс " работает"
P(&FULL); // P(Буфер не пуст)
P(&SEMBUF); // P(Операция с буфером )
char s=BUFF[fst]; // Получить из буфера
fst=(fst+1)%N; // и вывести
textbackground(BLUE);
putch(s);
V(&SEMBUF); // V(Операция с буфером )
V(&EMPTY); // V(Буфер не полон )
}}
void produce() // Процесс - производитель
{ while(1)
{
if (kbhit()) { // Если есть ввод
char c=getch();
textbackground(BLACK);
putch(c);
if (c=='\r') STOP++;
P(&EMPTY); // P(Буфер не полон )
P(&SEMBUF); // P(Операция с буфером )
textbackground(MAGENTA);
putch(c);
BUFF[lst]=c; // Поместить в буфер
lst=(lst+1)%N;
V(&SEMBUF); // V(Операция с буфером )
V(&FULL); // V(Буфер не пуст )
}}}