Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / Old / Лекция 12

.doc
Скачиваний:
39
Добавлен:
20.06.2014
Размер:
155.65 Кб
Скачать

ЛЕКЦИЯ 12

Классические проблемы межпроцессорного взаимодействия

Проблема обедающих философов.

Классическая проблема, сформулированная Дейкстрой в 1965 г.

5 философов проводят свою жизнь за размышлениями и едой. На столе стоит кастрюля со спагетти, у каждого философа есть тарелка. Между каждыми тарелками лежит одна вилка.

Рис.1

Собственно, проблема в том, что спагетти очень скользкие, и философу нужно две вилки, чтобы с ними управиться. Получив две вилки одновременно, философ ест, затем кладет вилки на стол (и размышляет).

Если хотя бы одна из вилок рядом с тарелкой занята, философ ждет.

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

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

Выход из положения: после захвата одной вилки проверить доступность правой. Если она недоступна, положить взятую вилку и подождать. Но это тоже неправильно: при одновременном захвате одной вилки все процессы будут вынуждены одновременно вернуть вилку на стол; подождав одинаковое количество времени, они опять одновременно повторят те же действия, и процессы зависнут.

Очевидно, что нельзя ждать одинаковое время; правильное решение – случайное ожидание. Тогда процессы зависнут только в том случае, если случайно выпадут одинаковые времена ожидания; вероятность этого низка. Такое решение применяется в сети Ethernet: если две станции посылают пакеты одновременно, возникает коллизия, и по протоколу каждая станция должна подождать случайное время, а потом повторить попытку. Это обычно хорошо работает. Однако, можно решить задачу без случайных чисел. Одно из решений неэффективное, но верное: защитить доступ к вилкам (и к еде вообще) мьютексом. Но тогда в каждый момент времени сможет есть только один философ. А из рис.1 видно, что одновременно могут есть 2.

Хорошее решение:

#define N 5 //количество философов

#define LEFT (i+N+1)%N //номер левого соседа

#define RIGHT (i=N-1)%N //номер правого соседа

#define THINKING 0 //думает

#define HUNGRY 1 //голоден

#define EATING 2 //ест

typedef int semaphore;

int state[N]; //массив для отслеживания состояний каждого философа

semaphore mutex=1;

semaphore S[N]; //каждому философу по семафору

//-------------------------------------------------------------------------------------------------

void philosopher (int i) //i – номер философа от 0 до N-1

{

while(1)

{

think();

take_forks(i);

eat();

put_forks(i);

}

}

void take_forks (int i)

{

wait(mutex); //вход в CS

state[i]=HUNGRY; //фиксация наличия голодного философа

test(i); //фиксация наличия голодного философа

release(mutex); //выход из CS

wait(S[i]); //блокировка, если вилок не досталось

}

void test (int i)

{

if(state[i]==HUNGRY && state[LEFT)!=EATING)

{

state[i]=EATING;

signal(S[i]);

}

}

Здесь философ как бы сам себя пропускает, открывая семафор. Если же условие ложно, семафор не откроется, и процедура take_forks будет блокирована.

void put_forks (int i)

{

wait(mutex); //вход в CS

state[i]=THINKING; //философ перестал есть

test(LEFT); // проверить, могут ли есть

test(RIGHT); //соседи слева и справа

release (mutex); //выход из CS

}

Проблема спящего брадобрея (или задача о парикмахерской).

В парикмахерской есть один брадобрей, его кресло и n стульев для посетителей. Если желающих побриться нет, брадобрей сидит в своем кресле и спит. Если в парикмахерскую приходит клиент, а брадобрей спит, клиент будит его. Если же брадобрей занят, то клиент садится на стул или уходит, если все стулья заняты. Необходимо запрограммировать брадобрея и посетителей.

Предлагаемое решение:

Используются три семафора:

Customers – для подсчета ожидающих посетителей (без того, который уже стрижется);

Barbers – количество простаивающих в ожидании брадобреев (0 или 1);

Mutex – для реализации взаимного исключения.

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

#define CHAIRS 5 //количество стульев

typedef int semaphore;

semaphore customers=0;

semaphore barbers=0;

semaphore mutex=1;

int waiting=0;

void barber(void)

{

while(1)

{

wait(customers); //ждать посетителей

wait(mutex); //вход в CS

waiting--; //ждет уже на одного меньше

signal(barbers); // на одного больще брадобрея доступно

release (mutex); // выход из СS

cut_hair(); //обслуживание – вне CS

}

}

void customer(void)

{

wait(mutex); //вход в CS

if(waiting<CHAIRS)

{

//если есть места

waiting++; //ожидающих на 1 больше

signal(customers); //разбудить брадобрея, если надо

release(mutex); //выход из CS

wait(barbers); //ждать, пока брадобрей не освободится

get_haircut(); //постричься

}

else release(mutex); //много посетителей – уйти, освободить mutex

}

Приходя, клиент запрашивает mutex; если придут сразу два клиента или более, только один из них сможет выполнять процедуру customer.

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

Замечание: не смотря на отсутствие передачи данных между процессами, рассмотренные задачи являются задачами межпроцессорного взаимодействия, т.к. они требуют синхронизации действий процессов.

Это в некоторой степени относится и к следующей задаче.

Проблема читателей и писателей

Эта задача моделирует доступ к БД. Разрешаются одновременно чтение, но требуется монопольный доступ на запись, и в это время не разрешается даже чтение.

semaphore mutex =1;

semaphore db =1; //контроль доступа к БД

int rc=0; //количество процессов чтения

void reader(void)

{

while(1)

{

wait(mutex); //вход в CS

rc++; //одним читающим больше

if(rc==1)

wait(db); //если этот читающий процесс первый – получить доступ к базе

//остальные – просто увеличивают на 1 число читающих (и тоже получают доступ)

release(mutex); //вход в CS

read_database(); //доступ к данным

wait(mutex); //вход в CS

rc--; //одним читающим меньше

if(rc==0) //если читающий был последним

signal(db); //отказаться от доступа к БД

release(mutex); //выход из CS

use_data(); //использовать данные

}

}

void writer(void)

{

while(1)

{

thinkup_data(); //подготовить данные

wait(db);

write_database(); //записать данные

signal(db); //лучше release(db) – это тоже вроде как mutex

}

}

В этом решении есть один недостаток. Читатели друг другу не мешают, и их может быть много. Но пишущий процесс не может писать, пока есть кто-то из читающих. Пока в базе есть хотя бы один читающий процесс, можно приходить любому количеству читателей. Так что, если активность читателей велика, пишущий процесс будет постоянно находиться в состоянии ожидания. Этого легко избежать, обеспечив полное разделение доступа – новый читающий процесс будет наравне с пишущим бороться за право доступа к БД. Однако, тогда задача как таковая пропадает – получается просто критическая секция.

Наилучшим решением является некоторое повышение приоритета пишущего процесса без изменения алгоритма.

Соседние файлы в папке Old