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

лекции по курсу параллельное прогр

.pdf
Скачиваний:
27
Добавлен:
26.02.2016
Размер:
834.64 Кб
Скачать

//Закрываем объект доступа к разделяемому коду.

CloseHandle(CritMutex);

}

//Первый поток: запись в массив данных.

DWORD WINAPI thread1(LPVOID par)

{ // Запись значений в массив.

//Запрос на вход в защищенный раздел.

DWORD dw = WaitForSingleObject(CritMutex,INFINITE); if(dw == WAIT_OBJECT_0)

{// Если объект освобожден корректно, то

//выполнение кода в защищенном разделе. for(int i = 0;i<1000;i++)

{

mas[i] = i;

}

//Выход из защищенного раздела:

//освобождаем объект для доступа

//к защищенному разделу других задач.

ReleaseMutex(CritMutex);

}

return 0;

}

// Второй поток: считывание данных из массива.

DWORD WINAPI thread2(LPVOID par)

{ // Считывание значений из массива. int j;

// Запрос на вход в защищенный раздел.

DWORD dw = WaitForSingleObject(CritMutex,INFINITE); if(dw == WAIT_OBJECT_0)

{// Если объект освобожден корректно, то

//выполнение кода в защищенном разделе. for(int i = 0;i<1000;i++)

{

j = mas[i];

}

//Выход из защищенного раздела:

//освобождаем объект для доступа

//к защищенному разделу других задач.

ReleaseMutex(CritMutex);

}

return 0;

}

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

http://mbo88.narod.ru/Ch9.html

Семафоры.

Семафор – реализуемая ядром операционной системы специальная переменная, которая доступна параллельным процессам для выполнения двух операций – закрытия и открытия (P и V операции). Операции неделимы и взаимно исключают друг друга. Терминология железнодорожная. Процесс устанавливает семафор на время критической секции. Другие процессы в это время приостановлены и ждут освобождения семафора.

Значение семафора – неотрицательное целое число. Операция V(s) : <s = s +1> увеличивает значение семафора.

Операция P(s) <await (s>0) s = s – 1>. Проверяет значение s и ждет, пока оно не станет положительным, после чего уменьшает его на 1. Ожидание является пассивным, то есть процесс приостанавливается. Операция V переводит один из ожидающих процессов в состояние работы.

Обычный семафор может принимать любые значения. Двоичный – только 0 и 1. Операционная система при работе с семафорами применяет справедливую стратегию, то есть возобновляет работу процессов в том порядке, в котором они приостанавливались, что обеспечивает для каждого процесса возможность продолжить работу.

Применение семафоров.

1.Критические секции. Можно использовать двоичный семафор. sem s = 1

….

P(s)

//критическая секция

V(s)

….

2.Барьерная синхронизация. – синхронизация по времени окончания опреаций в разных процессах.

Требования – ни один из процессов не должен перейти барьер, пока к нему не подошли все процессы. Например для двух процессов

Sem arrive1 = 0 ; arrive 2 = 1;

//1 процесс

….

V(arrive1); //сигнал о прибытии P(arrive2); // ожидание другого процесса

//2 процесс

….

V(arrive2);

P(arrive1);

Для n процессов нужен массив семафоров. Каждый процесс выполняет операцию V для своего элемента массива, а затем выполняет P для всех остальных семафоров.

3.Задача о производителе и потребителе. Производитель производит данные, помещая их в кольцевой буфер из n записей. Потребитель их обрабатывает, выбирая из буфера. Если буфер заполняется, то производитель должен подождать. Если буфер пуст, то ждет потребитель

Var Buf: array [ 0..N-1] of Type; Var head, tail: integer;

empty, full: sem;

head:=0; tail:=0; empty:=n; full =0 ;

//производитель

While true do Begin

//создать data P(empty);

Buf[tail]:=data; tail:=(tail+1) mod n; V(full);

End

//потребитель

While true do Begin

P(full);

Result:=Buf[head]; head:=(head+1) mod n; V(empty);

End

Семафоры играю роль счетчиков ресурсов. Empty считает количество пустых ячеек, Full – заполненных.

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

4. Задача об обедающих философах.

Пять философов сидят за круглым столом. Между ними по вилке. Каждый ест двумя вилками. Каждый некоторе время думает, затем пытается поесть. Нужно смоделировать их поведение. Программа должна избегать ситуации, когда все голодны, но каждый взял по вилке – сл-но ни один не может взять обе вилки.

Цикл будет такой

While True do Begin

// думаем sleep(random(1000)); //берем вилки // едим

sleep(random(1000)); //отдаем вилки

end;

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

5.Задача о читателях и писателях. Есть БД. Есть процессы читатели , которые могут одновременно читать данные. Есть писатели, которые имеют искл. доступ к

данным. Читатели конкурируют с писателями, писатели – между собой. Есть два способа решения этой задачи. Первый – как задачи взаимного исключения.

Для этого заведем переменные nr – число читателей, семафоры rw – для исключения доступа к БД читателей и писателей, m – для блокировки доступа читателей к nr.

// Writer

P(rw); //пишем

V(rw)

//reader

P(m)

nr: = nr +1;

if nr =1 then P(rw); //первый читатель – блокировать доступ

V(m)

Читать

P(m); Nr:=nr-1;

if nr = 0 then V(rw); // последний читатель – снять доступ.

V(m)

Преимущество будет у читателей. Пока они читают, писатели не смогут писать. Новый читатель получает преимущество перед писателем. Это несправедливо.

Рассмотрим решение с помощью условной синхронизации.

Будем подсчитывать число писателей nw и читателей nr. Множество хороших состояний параллельной программы будет описываться следующим условием

((nr = 0) or (nw =0)) and (nw<=1)

Отсюда получается код для читателей и писателей

//reader

<await (nw=0) nr:=nr+1>

чтение

<nr:=nr-1> //writer

<await (nw=0 and nr=0) nw:=nw+1>

чтение

<nw:=nw-1>

Для реализации await используем метод передачи эстафеты. Этот метод позволяет реализовать любую условную синхронизацию.

С каждым условием свяжем семафор и счетчик с нулевыми нач значениями. Семафор будем использовать для приостановки процесса пока условие защиты не станет истинным. Счетчик будет хранить число приостановленных процессов. Для нашей задачи нужно два семафора r и w, а также два счетчика dr и dw – количество ожидающих писателей и читателей. Семафор е используется для входа в неделимые блоки (кр.секции).

Для выход используется следующий код (Signal). Он сбрасывает один из трех семафоров – если нет писателей, но есть ожидающий читатель – семафор r, если нет писателей и читателей – w, иначе – e.

If (nw=0) and (dr >0)

Begin dec(dr); V(r) end

Else

If (nr=0) and (nw=0) and (dw >0)

Begin dec(dw); V(w) end

Else

V(e)

//reader

p(e);

if (nw>0) begin

dr:=dr+1;

v(e);

p(r)

End;

Nr=Nr+1;

Signal; //читаем

P(e); Nr:=Nr-1; Signal;

//writer

p(e);

if (nr>0 or nw>0) begin

dw:=dw+1;

v(e);

p(w)

End;

Nw=Nw+1;

V(e); //пишем

P(e); Nw:=Nw-1; Signal;

Из трех семафоров в листинге только один может иметь значение 1. Каждый код начинается с P и заканчивается V - сл-но код выполняется со взаимным исключением. Метод называется метод передачи эстафеты из-за способа выработки сигнала семафорами. Процесс внутри кр секции получил эстафету. При выходе из кр секции он передает эстафету следующему ожидающему процессу. Если ни один из процессов не ожидает, то эстафету получает следующий процесс, взывавший P(e).

Сигнальный код можно упростить, учитывая неравенства для dr,dw,nr,nw в каждом месте программы. Получим код

//reader

p(e);

if (nw>0) // or (dw>0) begin

dr:=dr+1;

v(e);

p(r)

End;

Nr=Nr+1;

if dr>0 then begin dr = dr-1; V(r) end else v(e);

//читаем

P(e);

Nr:=Nr-1;

If (nr=0) and (dw >0)

Begin dec(dw); V(w) end

Else V(e)

//writer

p(e);

if (nr>0 or nw>0) begin

dw:=dw+1;

v(e);

p(w)

End;

Nw=Nw+1;

V(e); //пишем

P(e); Nw:=Nw-1; If (dr >0)

Begin dec(dr); V(r) end Else

If (dw >0)

Begin dec(dw); V(w) end Else

V(e)

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

4.4. Механизм семафоров

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

Функция создания объекта типа семафор:

HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName );

Параметры:

lpSemaphoreAttributes - указатель на структуру SECURITY_ATTRIBUTES , который определяет, может ли возвращаемый дескриптор наследоваться порожденными процессами. Если NULL, то дескриптор не может наследоваться.

lInitialCount - определяет начальное значение семафора, которое должно быть не меньше нуля и не больше lMaximumCount. Семафор установлен, если его значение больше нуля, и не установлен, если его значение равно нулю. Счетчик семафора увеличивается при вызове функции ReleaseSemaphore.

lMaximumCount - определяет максимальное значение семафора;

lpName - указывает на строку, определяющую имя семафора, если NULL, то семафор открывается без имени.

Функция возврата дескриптора существующего именованного семафора:

HANDLE OpenSemaphore(

DWORD dwDesiredAccess, // access flag

BOOL bInheritHandle,

// inherit flag

LPCTSTR lpName

// pointer to semaphore-object name

);

Функция увеличения счетчика семафора на заданное число:

BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount,LPLONG lpPreviousCount );

Параметры:

 

hSemaphore

- дескриптор семафора;

lReleaseCount

- определяет, на сколько увеличивать счетчик семафора;

lpPreviousCount - указатель на 32-битную переменную с предыдущим значением счетчика (NULL, если предыдущее значение не требуется).

Функция WaitForSingleObject определяет, свободен ли семафор, и если он свободен, то уменьшает значение счетчика семафора на 1, в противном случае поток будет приостановлен, пока семафор не освободится.

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

Листинг 3. Ограничение доступа к массиву с использованием классических семафоров

//Массив значений. int mas[1000];

//Семафор, регулирующий доступ к критическому разделу.

HANDLE Semaph;

{

...

//Создаем семафор;

Semaph = CreateSemaphore(NULL, 1, 1, NULL);

//Запускаем потоки

CreateThread(NULL,0,thread1,NULL,0,NULL);

CreateThread(NULL,0,thread2,NULL,0,NULL);

... // Текст программы.

//Удаляем семафор

CloseHandle(Semaph);

}

// Первый поток: запись в массив данных.

DWORD WINAPI thread1(LPVOID par)

{// Запись значения в массив.

//Запрос на вход в критический раздел.

WaitForSingleObject(Semaph, INFINITE);

//Выполнение кода в критическом разделе. for(int i = 0;i<1000;i++)

{

mas[i] = i;

}

//Выход из критического раздела:

//освобождаем семафор для доступа

//других задач к критическому разделу

ReleaseSemaphore(Semaph, 1, NULL);

//завершаем поток

return 0;

}

// Второй поток: считывание данных из массива.

DWORD WINAPI thread2(LPVOID par)

{ // Считывание значений из массива. int j;

//Запрос на вход в критический раздел.

WaitForSingleObject(Semaph, INFINITE);

//Выполнение кода в критическом разделе. for(int i = 0;i<1000;i++)

{

j = mas[i];

}

//Выход из критического раздела:

//освобождаем семафор для доступа

//других задач к критическому разделу

ReleaseSemaphore(Semaph, 1, NULL);

//завершаем поток

return 0;

}

Реализация многозадачности и многопоточности в UNIX. Средства управления процессами

Функция fork – клонирует текущий процесс. Возвращает идентификатор потомка в род. процессе и ноль – в дочернем процессе.

Функции execl,execv позволяют заменить текущий процесс новым. Функция waitpid позволяет ожидать окончания порожденного процесса Функция kill посылает сигнал процессу

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

Для реализации многопоточности используется библиотека PTHREAD.

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

Для условной синхронизации в библиотеке PTHREAD имеются условные переменные (condvar). С условной переменной связана очередь потоков, ожидающих ее освобождения. Поток может проверить, пуста ли очередт (empty), встать в очередь (wait), запустить первый поток из очереди (signal).

Использование условных переменных позволяет упростить сложное взаимодействие потоков, реализовать различные стратегии планирования.

Параллельные алгоритмы.

Для обычных алгоритмов важное понятие – выч. Сложность. Для пар. Алгоритмов вводится понятие коэффициент ускорения (насколько ПА быстрее оптимвльного обычного). Например для сорт О(nlogn). Если пар алгоритм имеет сложность O(n) то КУ будет O(log n).

Другое понятие – стоимость пар алгоритма – произведение количества процессоров на сложность алгоритма. Если сортировка имеет сложность O(n) и требует столько процессоров, сколько исх данных, то стоимость будет O(n^2).

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

Макконелл. Основы современных алгоритмов.

Распределенные вычисления.

Используется, если отсутствует разделяемая память – кластеры, компьютерные сети, гибридные архитектуры. В этом случае процессы не могут взаимодействовать с помощью разделяемых переменных. Вместо этого они должны обмениваться сообщениями.

Как правило, программирование для сетевых кластеров отличается от привычной модели программирования для многозадачных систем, построенных на базе одного или множества процессоров. Часто в таких системах необходимо обеспечить только синхронизацию процессов, и нет нужды задумываться об особых способах обмена информацией между ними, т.к. данные обычно располагаются в общей разделяемой памяти. В сети реализация такого механизма затруднительно из-за высоких накладных расходов, связанных с необходимостью предоставлять каждому процессу копию одной и той же разделяемой памяти для работы. Поэтому, обычно, при программировании для сетевых кластеров используется SPMD-технология [8] (Single Program – Multiple Data, одна программа – множественные данные). Идея SPMD в том, чтобы поделить большой массив информации между одинаковыми процессами, которые будут вести обработку своей части данных (рис. 1).

Входныеданные

Результаты

 

Основной

 

процесс

 

(мастер)

ДПО 1

Процесс 1

 

ДДО 1

 

ДПО 2

Процесс 2

ДДО 2

.. .

 

ДПО N

 

Процесс N

ДДО N

ДДО – данные для обработки ДПО – данные после обработки

Рис. 1. Схема взаимодействия частей SPMD-программы.

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

Передача сообщений должна обеспечивать синхронизацию процессов. Для этого вводится понятие канала. Каждый канал обеспечивает путь взаимодействия процессов. Разработано несколько механизмов распределенного программирования. Они отличаются способами использования каналов и синхронизации взаимодействия. Каналы бывают однонаправленными и двунаправленными, обеспечивают асинхронную (неблокирующую) и синхронную передачу сообщений. Более высокоуровневые механизмы – удаленный вызов процедур и рандеву.

Простейший способ взаимодействия процессов в распределенных системах – асинхронная передача сообщений.

Канал – очередь сообщений, которые уже отправлены, но еще не получены. Допустима операция send channel (x), ставящая x в очередь на отправку в канал channel. Очередь теоретически не ограничена, поэтому операция send не вызывает задержку и является неблокирующей.

Для получения сообщения используется операция receive channel (x). Процесс приостанавливается и ожидает, пока в очереди канала не появится сообщение. Поскольку канал является очередью FIFO, то сообщения принимаются в порядке передачи.

Каналы глобальны относительно процессов и могут использоваться в любом процессе. Пример использования каналов – сортировка с помощью алгоритма сеть слияния. Сеть слияния использует процессы для слияния упорядоченных последовательностей Процесс Merge циклически сравнивает числа, полученные из потоков in1 и in2 и отправляет в out меньшее из них.

Procedure Merge begin

receive(in1, v1); receive(in2, v2);

while (v1<> eof) and (v2<>eof) do

if v1<= v2 then begin send(out, v1); receive(in1, v1); end