Опубликован: 01.10.2013 | Доступ: свободный | Студентов: 255 / 19 | Длительность: 24:58:00
ISBN: 978-5-9963-0223-9
Специальности: Разработчик аппаратуры
Лекция 3:

Особенности использования программных инструментальных платформ параллельных вычислительных систем общего назначения

< Лекция 2 || Лекция 3: 1234 || Лекция 4 >

2.2. Средства поддержки параллелизма в системах программирования общего назначения

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

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

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

Если при решении задачи умножения матриц разбить матрицу произведения (k*m)*(m*n) на подматрицы размера m*n, то для вычисления каждой такой подматрицы достаточно иметь m строк первого сомножителя и n столбцов второго. Разделив вычисления по независимым процессорам, можно ценой дублирования исходных данных значительно сократить время получения результата.

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

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

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

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

Современные системы программирования для вычислительных систем общего назначения содержат механизмы, поддерживающие параллелизм на уровне потоков исполнения. Механизмы параллелизма, например, включены в интегрированные среды разработки программ, использующих в качестве инструментальных языков Java, C#, C++.

В качестве примера рассмотрим средства реализации потоков исполнения в С# [270]. Многопоточная программа состоит из двух или больше частей, которые могут выполняться одновременно. Каждая часть такой программы рассматривается как поток исполнения ( thread ). Каждый поток определяет собственную трассу выполнения операций. Таким образом, мно-гопоточность представляет собой специальную форму многозадачности. Многопоточное программирование опирается на сочетание средств, предусмотренных языком С#, и классов, определенных в среде . NET Framework.

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

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

Процессно-ориентированная многозадачность обеспечивает одновременное выполнение программ, а поточно-ориентированная - одновременное выполнение частей одной и той же программы.

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

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

Язык С# и среда . NET Framework поддерживают как процессно-, так и поточно-ориентированную многозадачность. Следовательно, используя С#, можно создавать как процессы, так и потоки, а затем ими управлять. Поскольку поддержка многопоточности является встроенной, С# значительно упрощает создание многопоточных программ по сравнению с C++.

Многопоточная система встроена в класс Thread, который инкапсулирует поток управления. В классе Thread определены методы и свойства для управления потоками.

Поток создается путем создания объекта типа Thread, а выполнение потока инициируется методом Start(). Для управления потоком предусмотрены средства проверки завершения потока и ожидания завершения потока:

  • свойство IsAlive возвращает значение true, если поток, для которого оно опрашивается, еще выполняется. В противном случае оно возвращает значение false.
  • метод Join(), вызов которого приводит к ожиданию завершения потока, для которого этот метод вызван.

Среда . NET Framework определяет два типа потоков: высокоприоритетные и фоновые. Единственное различие между ними состоит в том, что процесс не завершится до тех пор, пока не завершится выполнение всех его высокоприоритетных потоков, при этом фоновые потоки заканчиваются автоматически после завершения всех высокоприоритетных потоков. По умолчанию любой поток создается как высокоприоритетный. При необходимости его можно сделать фоновым с помощью свойства IsBackground.

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

После старта дочерний поток получает стандартное значение приоритета. Его можно изменить с помощью свойства Priority.

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

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

Средство блокировки встроено в язык С#, поэтому доступ ко всем объектам может быть синхронизирован. Синхронизация поддерживается ключевым словом lock.

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

Схема организации программы "Источник - Приемник"

Рис. 2.5. Схема организации программы "Источник - Приемник"

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

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

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

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

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

Человек выступает в роли сотрудника предприятия и клиента банка, в котором он хранит вклад. Текущий вклад составляет 200 условных единиц. Банк ежемесячно начисляет клиенту 5 % от суммы вклада и выдает клиенту справку о состоянии вклада. Человек работает на двух предприятиях. Ежемесячный оклад на обоих предприятиях одинаков и составляет 100 условных единиц. Каждое из предприятий ежемесячно выплачивает сотруднику оклад, произведя предварительно налоговые отчисления в размере 10 %. Полученную сумму на каждом предприятии человек полностью переводит на свой вклад в банке.

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

Программа организована по схеме "Источник - Приемник" и содержит 5 классов. Комментарии и имена переменных достаточно полно раскрывают смысл методов и объектов, поэтому остановимся только на основных моментах.

class Человек {
public string фамилия;
public double оклад;
public double сумма;
public double вклад;
public Человек(string фамилия, double оклад, double вклад)
{
this.фамилия = фамилия; this.оклад = оклад; this.вклад = вклад;
}
//Объявление делегата для представления методов, принимающих
//в качестве аргумента любой объект класса Человек
public delegate void Расчет(Человек чел); }

Все операции с окладом и вкладом конкретного человека будут выполняться методами с одинаковой сигнатурой: void ИмяМетода (Человек чел). Представителем таких методов будет делегат типа " Расчет ", объявленный в классе " Человек ".

class Предприятие {
public static double налог = 10.0;
//Обработчик события "Окончание месяца" - начисление зарплаты
public void Начислить(Человек сотр)
{
сотр.сумма = сотр.оклад; }
//Обработчик события "Окончание месяца" //вычет налоговых отчислений public void Вычесть(Человек сотр) {
сотр.сумма = сотр.оклад * (100.0 - налог) / 100.0; } }

Класс служит типом для создания объектов - приемников извещений о событии " Окончание месяца ". Класс реализует обработчики событий, выполняющие начисление заработной платы и налоговые вычеты.

class Банк {
public static double процент = 5.0;
//Обработчик события "Окончание месяца" - внесение вклада //в размере месячной зарплаты public void Внести(Человек клиент) {
клиент.вклад = клиент.вклад + клиент.сумма;
клиент.сумма = 0; }
//Обработчик события "Окончание месяца" - начисление процентов
public void Пересчитать(Человек клиент)
{
клиент.вклад = клиент.вклад * (100.0 + процент) / 100.0; }
//Обработчик события "Окончание месяца" - 
//выдача справки клиенту о текущей величине вклада 
public void Сообщить(Человек клиент) 
 {
   Console.WriteLine("Вклад {0}:{1:f2}", клиент.фамилия, клиент.вклад); 
  } }

Класс служит типом для создания объектов - приемников извещений о событии "Окончание месяца". Класс реализует обработчики событий, выполняющие операции с вкладом и выдачу справки о текущемм состоянии вклада.

//Класc, объявляющий событие и выполняющий рассылку извещения
//о событии для всех объектов, которые подписались
//на это событие
class Извещение
{
 Человек чел;
 public Извещение(Человек чел) { this.чел = чел; }
 //Событие, связанное с окончанием текущего месяца public event Человек.Расчет Месяц;
 //Рассылка ивещений о событии для всех подписавшихся
 public void Разослать()
 {
  if (Месяц != null) Месяц(чел); 
 } 
}
< Лекция 2 || Лекция 3: 1234 || Лекция 4 >
Евгений Акимов
Евгений Акимов

Добрый день!

 

Скажите, пожалуйста,планируется ли продолжение курсов по нанотехнологиям?

Спасибо,

Евгений