В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 27
Текст из файла (страница 27)
5.4. Связывание трансляционных модулей
Трансляционные модули связываются для того, чтобы взаимодействовать как
части единой программы. При этом приходится называть имена партнеров по
связыванию.
Выделим два основных вида связывания, которые назовем односторонним и
двусторонним соответственно. При одностороннем связывании лишь один из двух
связываемых модулей называет имя своего партнера. При двустороннем - оба. В
стандартном Паскале и Бейсике трансляционных модулей нет, однако процедуры
вполне можно считать модулями (но не трансляционными, а логическими). В
Фортране имеются настоящие трансляционные модули (которые так и называются -
"модулями", а иногда "программными единицами"). Имеются трансляционные
модули и во всех современных диалектах Паскаля (unit в Турбо Паскале начиная
с версии 4.0, а также в проекте нового стандарта ИСО). В перечисленных
случаях применяется только одностороннее связывание модуля с контекстом (в
нужном контексте указывается имя требуемого модуля, но в самом модуле явные
ссылки на контекст отсутствуют; например, в процедуре не перечисляются ее
вызовы).
5.4.1. Модули в Аде.
Трансляционный модуль в Аде - это такой программный сегмент, все
внешние связи которого оформлены как связи с трансляционной библиотекой. Для
трансляционных модулей применяются оба вида связывания. С односторонним
связыванием мы уже фактически познакомились, когда применяли указание
контекста (with). Например, трансляционными модулями были: спецификация
пакета управление_сетями, процедура построение_сети, родовой сегмент
"очередь". Все это примеры так называемых первичных или открытых (library)
модулей. Название "открытых" отражает факт, что в силу односторонней связи
этими модулями можно пользоваться открыто, в любом месте программы, для
чего достаточно употребить соответствующее указание контекста.
Тело пакета управление_сетью и вообще тела подпрограмм и пакетов без
внешних связей (кроме связи со "своей" спецификацией) служат примерами так
называемых вторичных модулей. Повторение в них тех же имен, что и в
первичных модулях, можно трактовать как указание двусторонней связи с
соответствующими спецификациями. Ведь на тела можно ссылаться извне только
через спецификации. При этом имя пакета или подпрограммы в заголовке
спецификации трактуется как указание на связь с соответствующим телом, а то
же имя в заголовке тела трактуется как указание на связь с соответсвующей
спецификацией. Так что связь действительно двусторонняя.
Когда же сами спецификации представляют собой внутренние компоненты
других модулей, то по-прежнему можно оформлять соответствующие таким
спецификациям тела пакетов, процедур и задач как вторичные модули, но для
этого нужно явно указать уже двустороннюю связь. Именно: в том модуле, где
находится спецификация, применяют так называемую заглушку, указывающую на
вторичный модуль, а в заголовке вторичного модуля явно указывают имя того
модуля, где стоит заглушка. Признаком двусторонней связи служит ключевое
слово separate.
Например, можно оформить как вторичный модуль тело любой процедуры из
пакета управление_сетями. Выберем процедуру "вставить" и функцию
"перечень_связей". Тогда в теле пакета вместо объявления этих подпрограмм
нужно написать заголовки вида
function перечень_связей (узел: имя_узла)
return BOOLEAN is separate;
procedure вставить (узел: in имя_узла) is separate;
-- перед нами две ссылки на вторичные модули,
-- две заглушки.
Соответствующие вторичные модули нужно оформить так:
separate (управление_сетью) -- указано местонахождение заглушки
function перечень_связей (узел: имя_узла) return BOOLEAN is
. . . -- тело как обычно
end перечень_связей;
separate (управление_сетью)
procedure вставить (узел: in имя_узла) is
. . . -- тело как обычно
end вставить;
Теперь вторичные модули можно транслировать отдельно. В Аде предписан
определенный (частичный) порядок раздельной трансляции. В частности, все
вторичные (secondary) модули следует транслировать после модулей с
соответствующими заглушками (т.е. после "старших" модулей, которые в свою
очередь могут быть как первичными, так и вторичными).
Итак, ко вторичному модулю можно "добраться" только через его партнера.
Поэтому в отличие от открытых библиотечных модулей их естественно называть
закрытыми. Свойство закрытости обеспечено применением явной двусторонней
связи.
5.5. Принцип защиты авторского права.
Подчеркнем, что закрытые модули появились в Аде не случайно. Они, с
одной стороны, естественное развитие разделения абстракции и конкретизации
(спецификации и реализации) до уровня модульности (ведь модуль -
материальное воплощение абстракции). С другой стороны, они обслуживают
важный принцип конструирования языка Ада, который можно было бы назвать
принципом защиты авторского права (языковыми средствами).
Дело в том, что тексты вторичных модулей можно вовсе не предоставлять
пользователю при продаже програмных изделий, созданных на Аде. Ему
передаются только спецификации - открытые модули и защищенные от чтения
оттранслированные вторичные. Тогда пользователь никак не сможет незаконно
добраться до вторичных модулей. При соответствующей защите от
несанкционированного доступа он не только не сможет неправильно пользоваться
ими, но не сможет и скопировать или употребить для построения других систем
(отдельно от закупленных). Принцип защиты авторского права получил
существенное развитие в идеологии так называемого об'ектно-ориентированного
программирования, восходящей еще к ЯП Симула-67 и в своем современном виде
воплощенной в таких новейших ЯП, как Смолток, Оберон, Си++ и Турбо Паскаль
5.5 (подробности - в разделе "Об'ектно-ориентированное программирование").
Замечание. С учетом идеологии конкретизирующего программирования
возникает соблазн рассматривать раздельно транслируемый модуль как
программу, управляющую транслятором (интерпретируемую транслятором) в
процессе раздельной трансляции. Результатом выполнения такой программы может
быть модуль загрузки, перечень обнаруженных ошибок и (или) изменения в
трансляционной библиотеке. При таком общем взгляде на раздельную трансляцию
управление ею становится столь же разнообразным и сложным, как
программирование в целом. Такой взгляд может оказаться полезным, когда
универсальный модуль рассматривается как источник (или генератор)
разнообразных версий программы, учитывающих особенности конкретных
применений.
Разнообразными становятся и способы извлечения из внешней среды
информации об указанных особенностях (параметры, анализ среды, диалог с
программистом и т.п.). Так что в общем случае создание универсальных модулей
в корне отличается от написания частей конкретных программ. Такие модули
становятся метапрограммами, описывающими процесс или результат создания
частей конкретных программ. Поэтому язык для написания универсальных модулей
и управления их связыванием в общем случае может сильно отличаться от
исходного языка программирования (хотя, с другой стороны, есть немало
аргументов в пользу их совпадения). Так что рассмотренный общий взгляд по
существу выводит за рамки собственно раздельной трансляции.
Настройка "универсальных" модулей на конкретное применение в Аде есть -
ее обслуживает аппарат родовых сегментов. Но конкретизация родовых сегментов
выполняется не при связывании собственно модулей, а позже, в рамках
последующей трансляции сегментов.
6. Асинхронные процессы
6.1. Основные проблемы
До сих пор мы занимались последовательным программированием и
соответствующими аспектами ЯП. Этот вид программирования характеризуется
представлением о единственном исполнителе, поведение которого и нужно
планировать. Во всяком случае, зто поведение всегда можно было мыслить как
некоторый процесс - последовательность действий из репертуара этого
исполнителя, строго упорядоченных во времени.
Основной стимул для расширения сферы наших интересов состоит в том, что
мир разнообразен и его моделирование последовательным процессом часто
оказывается неадекватным. Поэтому займемся языковыми средствами,
предназначенными для планирования поведения некоторого коллектива
исполнителей. Мы употребляем слово "коллектив", а не просто "совокупность"
исполнителей, чтобы подчеркнуть, что нас интересуют, как правило, только
взаимодействующие исполнители, совместно решающие некоторую проблему.
Поведение каждого из них будем по-прежнему мыслить как некоторый процесс,
но будем считать, что поведение коллектива в целом в общем случае никакой
строго упорядоченной во времени последовательностью действий не описывается.
Однако оно естественным образом описывается совокупностью (коллективом)
взаимодействующих последовательных процессов. Так что соответствующие
языковые средства должны предусматривать описание такого коллектива
процессов (в частности, их запуск и завершение), и (самое сложное, важное и
интересное) описание их взаимодействия. Подчеркнем, что если взаимодействие
процессов отсутствует, то коллектив распадается на отдельные процессы,
задача управления которыми решается в последовательном программировании.
Подчеркнем, что коллективы исполнителей сплошь и рядом встречаются в
реальной жизни: сам компьютер естественно представлять коллективом
исполнителей-компонент (центральный процессор, память, периферия), сети
компьютеров - также коллектив исполнителей; производственные, военные,
информационные процессы также естественно группировать в коллективы.
Наконец, в некоторых задачах коллективы процессов возникают в целях
оптимизации (матричные операции, сеточные задачи и т.п.).
Как уже сказано, упорядоченность отдельных событий (фактов поведения) в
разных процессах часто можно считать несущественной для задачи, решаемой
всем коллективом. Поэтому обычно такой упорядоченностью можно пренебречь и
считать процессы асинхронными, в частности, исполняемыми параллельно на
подходящем коллективе процессоров (реальных или виртуальных).
По этой причине соответствующее программирование называют
"параллельным", а соответствующие ЯП - параллельными или языками
параллельного программирования (ЯПП).
С другой стороны, когда упорядоченностью событий в отдельных процессах
пренебречь нельзя, программа должна ее явно или неявно предписывать, для
чего в ЯПП должны быть соответствующие средства синхронизации асинхронных
процессов. Важнейшая цель синхронизации - организация обмена информацией
между исполнителями (асинхронными процессами) без которого их взаимодействие
невозможно.
Итак, нас будут интересовать особенности ЯПП и прежде всего средства
синхронизации и обмена как частные случаи средств взаимодействия процессов.
Основные проблемы (кроме обычных для последовательного
программирования) фактически названы. Это синхронизация, обмен (данными),
запуск процессов, завершение процессов.
Наиболее интересные работы в области ЯПП прямо или косвенно имеют
следующую перспективную цель: выработать систему понятий, позволяющую
концентрироваться на решении специфических проблем параллельного
программирования тогда и там, когда и где это существенно для решаемой
задачи, а не для выбранных средств программирования. Другими словами, цель
состоит в том, чтобы программировать асинхронные процессы со степенью
комфорта, не уступающей лучшим достижениям последовательного
программирования.
Эта цель еще впереди, но есть ряд достижений, с которыми мы и
познакомимся.
Рассмотрим характерную для параллельного программирования задачу о
поставщике и потребителе некоторой информации. Предполагается, что эти два
исполнителя способны работать совершенно независимо, кроме ситуаций, когда
они непосредственно заняты обменом информации.
Наша цель: продемонстрировать как пользу, так и проблемы параллельного