Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Архитектура компьютеров / 10_Большие системы.doc
Скачиваний:
57
Добавлен:
20.03.2015
Размер:
389.63 Кб
Скачать

10.8. Общая память и передача сообщений

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

Предположим, необходимо вычислить скалярное произведение двух N-элементных векторов. Последовательная программа решения этой задачи предна­значена для выполнения на одном процессоре (рис. 10.15). Логика программы по­нятна. Инструкции считывания загружают с диска (или с какого-либо другого устройства ввода-вывода) в основную память значения двух векторов. Эта задача выполняется операционной системой. Давайте попробуем распараллелить задачу для реализации на двух процессорах. Сделать это можно в цикле, который вычис­ляет произведение очередной пары элементов, а результаты суммирует.

10.8.1. Система с общей памятью

Первый вариант программы для двух процессоров показан на рис. 10.16. В случае запуска программы на одном процессоре она загружает векторы в память и при­сваивает переменной dot_product значение 0. При параллельном выполнении программы на двух процессорах часть вычислений, требуемых для получения скалярного произведения, необходимо возложить на один из них. Для этого мы создаем отдельный поток, предназначенный для выполнения на таком процессоре.

integer array a[l...N], b[l...N]

integer dot_product

..

read a[l...N] from vector_a

read b[l...N] from vector_b

dot_product :=0

do_dot(a, b)

print dot_product

..

do_dot (integer array x[l...n], integer array y[l...N])

for k:= 1 to N

dot_product := dot_product + x[k]*y[k]

end

end

Рис. 10.15. Последовательная программа вычисления скалярного произведения

Поток — это независимый фрагмент программы. Потоки могут соответство­вать разным фрагментам кода программы или одному и тому же фрагменту, вы­полняемому несколько раз в различных условиях. Главное, что они могут выпол­няться параллельно, как отдельные программы, поэтому их можно запускать на разных процессорах. Вместе с тем потоки являются частями одной и той же про­граммы, реализуемыми в одном и том же адресном пространстве. В типичном од­нопроцессорном окружении каждая программа имеет один поток выполнения.

В программе на рис. 10.16 новый поток создается посредством инструкции create_thread. Вызвав процедуру do_dot, этот поток завершает свою работу. Опе­рационная система присваивает новому потоку идентификационный номер 1. Да­лее первый процессор выполняет инструкцию do_dot (a,b) как поток 0. Инструк­ция id := mypid() присваивает переменной id идентификационный номер потока. С помощью переменной id в цикле for мы определяем, какая половина векторов а и b должна обрабатываться данным потоком.

Критической секцией процедуры do_dot является код, изменяющий значение переменной dot_product. Каждый поток должен получать монопольный доступ к указанной переменной. Для этого используется описанный в разделе 10.6.1 ме­ханизм блокировок. Поток 0 не идет далее инструкции-барьера barrier в процеду­ре do_dot, пока другой поток не достигнет той же синхронизационной точки. Это необходимо для того, чтобы оба потока завершили свои вычисления до того, как поток 0 сможет напечатать конечный результат. Инструкцию-барьер можно реа­лизовать двумя способами. Простейший подход заключается в использовании общей переменной, такой как done (рис. 10.16). Она инициализируется значени­ем количества потоков (в нашем примере их два), и когда каждый поток достига­ет барьера, ее значение уменьшается на единицу.

shared integer array a[l...N], b[l...N]

shared integer dot_product

shared lock dot_product_lock

shared barrier done

..

read a[l...N] from vector_a

read b[l...N] from vector_b

dot_product :=0

create_thread (do_dot, a, b)

do_dot (a, b)

print dot_product

..

do_dot (integer array x[l...N], integer array y[l...N])

private integer id

id := mypid()

for k :=(id*N/2) + 1 to (id+l)*N/2

lock (dot_product_lock)

dot_product :- dot_product + x[k] * y[k]

unlock (dot_product_lock)

end

barrier (done)

end

Рис. 10.16. Первый вариант программы вычисления скалярного произведения на двух процессорах в системе с общей памятьюУ программы на рис. 10.16 имеется один существенный недостаток. Исполь­зуемый в ней механизм блокировок не позволяет по-настоящему параллельно выполнять два потока, поскольку оба потока постоянно пытаются записать дан­ные в одну и ту же общую переменную dot_product, а делать это одновременно они не могут. Таким образом, потенциально параллельные вычисления на самом деле выполняются последовательно.

Чтобы добиться настоящего параллелизма, можно так модифицировать програм­му, как показано на рис. 10.17. Вместо использования в цикле for общей переменной dot_product мы задействовали локальную переменную local_dot_ product, в которой накапливается частичное скалярное произведение, вычисляемое каждым из пото­ков. Вход в критическую секцию, где каждый поток обновляет общую переменную dot_product, производится только по окончании цикла. После такой модификации циклы for обоих потоков действительно могут выполняться параллельно.

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

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

shared integer array a[l...N], b[l...N]

shared integer dot_product

shared lock dot_product_lock

shared barrier done

..

read a[l...N] from vector_a

read b[l...N] from vector_b

dot_product :=0

create_thread (do_dot, a, b)

do_dot (a, b)

print dot_product

..

do_dot (integer array x[l...N], integer array y[l...N])

private integer local_dot_product

private integer id

id := mypid()

local_dot_product := 0

for k :-(id*N/2) + 1 to (id+l)*N/2

local_dot_product := local_dot_product + x[k] * y[k]

end

lock (dot_product_lock)

dot_product :- dot_product + local_dot_product

unlock (dot_product_lock) ,

barrier (done)

end

Рис. 10.17. Эффективная программа для вычисления скалярного произведения на двух процессорах в системе с общей памятью

Соседние файлы в папке Архитектура компьютеров