Основы объектно-ориентированного проектирования

         

Предварительный просмотр


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

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

Расширение, полностью охватывающее параллельность и распределенность, будет самым минимальным из всех возможных: к последовательным обозначениям добавляется единственное новое ключевое слово - separate. Почему это возможно? Мы используем основную схему ОО-вычислений: вызов компонента x.f (a), выполняемый от имени некоторого объекта O1, и вызывающий компонент f объекта O2, присоединенного к x с аргументом a. Но сейчас вместо одного процессора, выполняющего операции всех объектов, мы рассчитываем на возможность использовать разные процессоры для O1 и O2, так что вычисление O1 может продолжаться, не ожидая завершения указанного вызова, поскольку он обрабатывается другим процессором.

Поскольку результат вызова сейчас зависит от того, обрабатываются ли объекты одним процессором или несколькими, в тексте программы об этом должно быть точно сказано для каждой сущности x. Поэтому требуется новое ключевое слово: вместо того, чтобы объявлять просто x: SOME_TYPE, будем объявлять x: separate SOME_TYPE, чтобы указать, что x обрабатывается отдельным процессором, так что вызовы с целью x могут выполняться параллельно с остальным вычислением.
При таком объявлении всякая команда создания create x.make (...) будет порождать новый процессор - новую ветвь управления - для обработки будущих вызовов x.

Нигде в тексте программы не требуется указывать, какой именно процессор нужно использовать. Все, что утверждается посредством объявления separate - это то, что два объекта обрабатываются различными процессорами, и это существенно влияет на семантику системы. Назначение конкретного процессора можно перенести на время исполнения. Мы также не устанавливаем заранее точную природу процессора: он может быть реализован как часть оборудования (компьютера), но может также оказаться заданием (процессом) операционной системы или, в случае многопоточной ОС, стать одной из нитей (потоков) задания. С точки зрения программы "процессор" - это абстрактное понятие; одно и то же параллельное приложение может выполняться на совершенно разных архитектурах (на одном компьютере с разделением времени, в распределенной сети со многими компьютерами, несколькими потоками одного задания под Unix или Windows) без всякого изменения его исходного текста. Все, что потребуется изменить, - это "Файл параллел ьной конфигурации" -_ ("Concurrency Configuration File"), задающий отображение абстрактных процессоров на физические ресурсы.

Определим ограничения, связанные с синхронизацией. Эти соглашения достаточно просты:

  • Клиенту не требуется никакого специального механизма для повторной синхронизации с сервером после того, как вызов x.f (a) для объявленной separate сущности x пойдет на параллельное выполнение. Клиент будет ждать столько, сколько необходимо, когда он запрашивает информацию об объекте с помощью вызова запроса, как в операторе value := x.some_query. Этот автоматический механизм называется ожидание по необходимости (wait by necessity).
  • Для получения исключительного доступа к отдельному объекту O2 достаточно использовать присоединенную к нему сущность a, объявленную как separate, в качестве аргумента соответствующего вызова, например, r(a).
  • Если у подпрограммы имеется предусловие, содержащее аргумент, объявленный как separate (например, такой как a), то клиенту придется ждать, пока это предусловие не выполнится.
  • Для контроля за работой ПО и предсказуемости результатов (в частности, поддержания инвариантов класса) нужно разрешать процессору, ответственному за объект, выполнять в каждый момент времени не более одной процедуры.
  • Однако иногда может потребоваться прервать выполнение некоторой процедуры, уступив ресурсы новому более приоритетному клиенту.Клиент, которого прервали, сможет произвести соответствующие корректирующие мероприятия; наиболее вероятно, что он повторит попытку после некоторого ожидания.
Это описание охватывает основные свойства механизма, позволяющего строить продвинутые параллельные и распределенные приложения, в полной мере используя ОО-методы от множественного наследования до проектирования по контракту. Далее мы рассмотрим этот механизм детально, забыв на время то, что прочли только что в этом кратком обзоре.


Содержание раздела