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

u_course

.pdf
Скачиваний:
39
Добавлен:
04.06.2015
Размер:
1.87 Mб
Скачать

Средства разработки параллельных программм

141

int main(int argc, char** argv)

{

int i, x[k+m];

DWORD dwThreadId[k+m],dw; HANDLE hThread[k+m];

hSemEmpty = CreateSemaphore(NULL,n,n,"Empty"); hSemFull = CreateSemaphore(NULL,0,n,"Full");

hMutexD = CreateMutex(NULL,FALSE,"MutexD"); hMutexF = CreateMutex(NULL,FALSE,"MutexF");

for(i=0;i<k;i++)

{

x[i] = i;

hThread[i] = CreateThread(NULL,0,Producer,(PVOID)&x[i], 0, &dwThreadId[i]);

if(!hThread)

printf("main process: thread %d not execute!",i);

}

for(i=k;i<k+m;i++)

{

x[i] = i;

hThread[i] = CreateThread(NULL,0,Consumer,(PVOID)&x[i], 0, &dwThreadId[i]); if(!hThread)

printf("main process: thread %d not execute!",i);

}

WaitForMultipleObjects(k+m,hThread,TRUE,INFINITE);

// закрытие описателей событий

CloseHandle(hSemFull);

CloseHandle(hSemEmpty);

CloseHandle(hMutexF);

CloseHandle(hMutexD); return 0;

}

КОНТРОЛЬНЫЕВОПРОСЫ

1.Раскройте понятие объекта ядра.

2.Каким образом объекты ядра совместно используются несколькими процессами?

3.Способы разделения объектов ядра в Win32 API.

4.Организация процесса в ОС MS Windows.

5.Параметры процесса в ОС MS Windows.

6.Порождения и завершение процесса с использованием Win32 API.

7.Особенности организации и использования потоков в Win32 API.

8.Параметры потока в ОС MS Windows.

Средства разработки параллельных программм

142

9.Порождения и завершение потока с использованием Win32 API.

10.Синхронизация потока в пользовательском режиме: Interlocked-

функции.

11.Синхронизация потока в пользовательском режиме: структура

CRITICAL_SECTION.

12.Синхронизация потоков с помощью объектов ядра. Использование Wait-функций.

13.Понятие события. Организация исключительного доступа к ресурсу

спомощью событий.

14.Функции Win32 API для работы с событием. Условная синхронизация с помощью событий.

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

16.Функции Win32 API для работы с семафорами. Условная синхронизация с помощью семофоров.

17.Понятие мьютекса. Организация исключительного доступа к ресурсу с помощью мьютексов.

18.Функции Win32 API для работы с мьютексами. Сходства и отличия семафоров и мьютексов в Win32 API.

19.Решение задачи о кольцевом буфере с использованием функций

Win32 API.

ГЛАВА5. ТЕХНОЛОГИЯПРОГРАММИРОВАНИЯ

OPENMP

POSIX-интерфейс для организации потоков (Pthreads) поддерживается практически на всех UNIX-системах, однако по многим причинам не подходит для практического программирования синхронных параллельных вычислений. Среди особенностей, делающих затруднительным использование библиотеки Pthread, можно назвать следующие:

нет поддержки языка программирования FORTRAN,

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

отсутствует поддержка параллелизма по данным,

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

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

Многие поставщики SMP-архитектур (Sun, HP, SGI) в своих компиляторах поддерживают спецдирективы для распараллеливания циклов. Однако эти наборы директив, во-первых, весьма ограничены, а во-вторых, несовместимы между собой. В результате разработчикам приходится распараллеливать приложение отдельно для каждой платформы. OpenMP является во многом обобщением и расширением упомянутых наборов директив.

Интерфейс OpenMP задуман как стандарт для программирования на масштабируемых SMP-системах (SSMP,NUMA,ccNUMA, и.т.д.) в модели общей памяти. В стандарт OpenMP входят спецификации набора директив компилятора, процедур и переменных среды. Примерами систем с общей памятью, масштабируемых до большого числа процессоров, могут служить су-

перкомпьютеры Cray Origin2000 (до 128 процессоров), HP 9000 V-class (до 32

процессоров в одном узле, а в конфигурации из 4 узлов - до 128 процессо-

ров), Sun Starfire (до 64 процессоров).

Стандарт OpenMP1 был разработан в 1997 г. как API, ориентированный на написание переносимых многопоточных приложений. Сначала он был основан на языке Fortran, но позднее оказался перенесенным на C и C++. Ниже рассматривается использование OpenMP в C и C++.

1 Основной источник информации - сервер http://www.openmp.org/. На сервере доступны спецификации, статьи, учебные материалы, ссылки.

Средства разработки параллельных программм

144

Технология OpenMP представляет программу как совокупность параллельных и последовательных областей. В момент запуска программы порождается нить-мастер1, которая начинает выполнение со стартовой точки. В последовательной области программы всегда исполняется только основная нить. Для поддержки параллелизма используется схема FORK/JOINT. При входе в параллельную область нить мастер порождает группу дополнитель-

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

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

OpenMP включает два базовых типа конструкций: директивы pragma и

функции исполняющей среды OpenMP.

Директивы pragma, как правило, указывают компилятору, как реализовать параллельное выполнение блоков кода. Они начинаются с конструкции

#pragma omp и игнорируются компилятором, не поддерживающим OpenMP. Функции OpenMP служат в основном для изменения и получения па-

раметров среды. Чтобы задействовать их в программу нужно включить заголовочный файл omp.h.

ДИРЕКТИВЫPRAGMA

Технология OpenMP предполагает разработку параллельных программ на основе их последовательных аналогов с использованием средств прекомпиляции. Для реализации параллельного выполнения блоков приложения нужно просто добавить в код директивы pragma и, если нужно, воспользоваться функциями библиотеки OpenMP периода выполнения.

Директивы pragma имеют следующий формат:

#pragma omp <директива> [раздел [ [,] раздел]...]

OpenMP поддерживает директивы parallel, for, parallel for, section, sections, single, master, critical, flush, ordered и atomic, которые определяют механизмы раз-

деления работы или конструкции синхронизации.

Раздел – это необязательный модификатор директивы, влияющий на ее поведение. Списки разделов, поддерживаемые каждой директивой, различаются, а пять директив (master, critical, flush, ordered и atomic) не поддерживают разделы.

1 Согласно общепринятой терминалогии технологии OpenMP потоки принято называть нитями. Далее в этой главе мы будем придерживаться этого термина.

Средства разработки параллельных программм

145

Директива parallel

Директива parallel создает параллельную область для следующего за ней структурированного блока. Общий синтаксис директивы имеет следующий вид:

#pragma omp parallel [раздел[ [,] раздел]...]

структурный блок

Она сообщает компилятору, что структурированный блок кода должен быть выполнен параллельно, в нескольких потоках. Количество порождаемых нитей храниться в переменной среды окружения OMP_NUM_THREADS (порождается OMP_NUM_THREADS-1 нить, поскольку нить-мастер уже порождена). Начальное значение этой переменной задается перед запуском программы и может изменяться в ходе ее выполнения.

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

В директиве parallel могут быть использованы следующие разделы.

if (<выражение>) – если выражение истинно, код блока исполняется

параллельно, иначе последовательно.

private (<список переменных>) – переменные, перечисленные в списке

раздела private являются локальными для каждй нити. Эти переменные инициализируются в каждой нити параллельной области значениями умолчанию.

firstprivate (<список переменных>) – перечисленные в списке раздела firstprivate являются локальными для каждй нити. Переменные получают начальное значение, которое присваевается этой переменной нитью-мастером

до директивы #pragma omp parallel.

default (shared | none) – определяет являются ли переменные общими

по умолчанию или тип их использования необходимо определить.

shared (<список переменных>) – переменные, перечисленные в списке

раздела shared разделяются всеми потоками програмы.

copyin (<список переменных>) – значения переменных, перечисленных в

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

reduction (оператор: <список переменных>) – описываются переменная и

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

Средства разработки параллельных программм

146

reduction применяется к каждой частной копии переменной, а также к исходному значению переменной.

Таблица. 5.1

Операторы раздела reduction

Оператор раздела reduction

Инициализированное

(каноническое) значение

+

0

*

1

-

0

&

~0 (каждый бит

установлен)

 

|

0

^

0

&&

1

||

0

По умолчанию все переменные в параллельной области являются общими, кроме следующих случаев:

индексы параллельных циклов for всегда локальные;

переменные, объявленные в параллельных областях;

любые переменные, указанные в разделах private, firstprivate, lastprivate и reduction.

Ниже приведен пример простейшей программы на OpenMP + C.

#pragma omp parallel

{

printf("Hello World!\n");

}

В двухпроцессорной системе предполагается получить следующее:

Hello World!

Hello World!

Тем не менее, результат может оказаться и таким:

HellHell oo WorWlodrl d!!

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

Средства разработки параллельных программм

147

гонок используют блокировки или сводят к минимуму обращения к общим ресурсам.

Директива for

Конструкция #pragma omp for относится к директивам разделения работы (work-sharing directive). Такие директивы применяются не для параллельного выполнения кода, а для логического распределения группы потоков.

Директива #pragma omp for сообщает, что при выполнении цикла for в параллельной области итерации цикла должны быть распределены между потоками группы. Она имеет следующий синтаксис:

# pragma omp for [раздел[ [,] раздел]...]

цикл for

В директиве for могут быть использованы следующие разделы.

private, firstprivate, reduction – определяются точно так же, как и в дирек-

тиве parallel.

lastprivate (<список переменных>) – переменные, перечисленные в списке

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

nowait – по завершению цикла барьерная синхронизация выполнять-

ся не будет (следует использовать эту опцию очень аккуратно).

ordered – код внутри этой конструкции будет выполнен последова-

тельно.

schedule(<тип>[, размер болока]) - определяет способ распределения

итераций. Параметр «тип» может принимать значения: static – итерации статически делятся на блоки указанного размера и распределяются между потоками; dynamic – итерации делятся на блоки указанного размера, освободившаяся нить берет на исполнение первый из еще не обработанных блоков; guided – то же, что и dynamic, только размер блока экспоненциально уменьшается;runtime – режим распределения итераций выбирается на основании значения перемен-

ной OMP_SCHEDULE

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

#pragma omp parallel

{

#pragma omp for

for(int i = 1; i < size; ++i) x[i] = (y[i-1] + y[i+1])/2;

}

Средства разработки параллельных программм

148

Если бы этот код выполнялся на четырехпроцессорном компьютере, а у переменной size было бы значение 100, то выполнение итераций цикла 1-25 могло бы быть поручено первому процессору, 26-50второму, 51-75третьему, а 76-99четвертому.

Следует отметить, что в конце параллельной области выполняется неявная барьерная синхронизация (barrier synchronization).

Так как циклы являются самыми распространенными конструкциями, где выполнение кода можно распараллелить, OpenMP поддерживает сокращенный способ записи комбинации директив #pragma omp parallel и #pragma

omp for:

#pragma omp parallel for for(int i = 1; i < size; ++i) x[i] = (y[i-1] + y[i+1])/2;

Кроме того, OpenMP налагает ограничения на циклы for, которые могут

быть включены в блок #pragma omp for или #pragma omp parallel for block. Циклы

for должны соответствовать следующему формату:

for([целочисленный тип] i = инвариант цикла;

i{<,>,=,<=,>=} инвариант цикла;

i{+,-}= инвариант цикла)

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

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

Эти требования введены для того, чтобы OpenMP мог при входе в цикл определить число итераций.

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

double x1=0; double x2=0; double dx=1;

// такой цикл распараллелить нельзя!!!

for (int i=0; dx<0.001; i++) { x2=f(x1); dx=fabs(x1-x2);

x1=x2;

}

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

Средства разработки параллельных программм

149

Рассмотрим пример программы, вычисляющей значение π, используя равенство

1

4

 

dy 4arctg

y

 

10.

 

 

1 y

2

0

 

 

 

 

 

Для вычисления интеграла используется квадратурная формула прямоугольников. Сумма насчитывается параллельно.

#include<omp.h>

#include<stdio.h>

double f(double y)

{ return(4.0/(1.0+y*y));}

main()

{

double w,x,sum,pi; int i;

int n = 1000; h = 1.0/n; sum = 0.0;

#pragma omp parallel for schedule(static,n/2) private(i,x) \ shared(h) reduction(+:sum)

for(i=0; i < n; i++)

{

x = h*(i+0.5); sum = sum + f(x);

}

pi = h*sum; printf("pi = %f\n",pi); return(0);

}

В приведенном примере цикл статически распараллеливается на 2 потока по n/2 итераций каждому, переменные i и x свои в каждом потоке, и переменная h – общая, для переменной sum используется редукция по операции сложения, результат редукции получит одноименная переменная нити мастера.

Параллельные секции

Конструкция sections используется для объявления разделов программного кода, выполнение которых необходимо распределить между несколькими потоками.

#pragma omp sections

{

#pragma omp section

{

<фрагмент 1>

}

Средства разработки параллельных программм

150

#pragma omp section

{

<фрагмент 2>

}

#pragma omp section

{

<фрагмент n>

}

}

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

#pragma omp sections nowait.

В следующем отрывке кода организовано распределение циклов и нециклических разделов кода между потоками:

#pragma omp parallel

{

#pragma omp for for (i=0; i<x; i++) Fn1();

#pragma omp sections

{

#pragma omp section

{

TaskA();

}

#pragma omp section

{

TaskB();

}

#pragma omp section

{

TaskC();

}

}

}

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]