Владимир Ситников писал(а):
2.2) Непонятно что значит вопрос "ПТП" с двумя выходами и возвратом управления. Что, если значение ПТП всегда равно <<Да>>? Нарисованный слева "возврат управления" будет как-нибудь влиять? Честно говоря, когда слева от ПТП нарисована иконка синхронизатора (ну или как её там) всё равно непонятно как предполагается обработка ПТП. Но я к тому, что "возврат управления" слева от иконки вопроса, у которой обе ветки идут вниз смотрится вообще непонятно.
Я машинально "переводил" примеры выше на базе оригинала. Для данного случая необходимо лезть в книжку Закревского (на основе чего были исходные примеры схем) и уточнять постановку задачи. Однако, в целом, у Закревского упрощённая модель понимания процессов: распределение действий во времени в зависимости от возникновения событий, плюс логические параллельные процессы -- дивергенция и конвергенция потоков управления. И нет как такового типового оператора "if", только события (на графе все события выглядят одинаково), и, по-видимому, из контекста необходимо понимать, что если ожидать нечего, то событие происходит сразу же, как будто простая "if"-проверка.
А в общем случае возникает необходимость определиться в вариациях операции "ожидание". К примеру, выше был акцент на операции yield в рамках паттерна "генератор". В таком случае yield возвращает "наверх" данные, выполняется временная приостановка алгоритма и "ожидание" возможности продолжить поток управления. Какие-то возможные последующие операции проверки условий не относятся к "ожиданию" события.
Возможны и иные варианты операции yield без явной передачи данных и явного ожидания события (косвенно). Выше в теме была затронута древняя Esterel, где, как и у упомянутых Закревского и Зюбина, техника моделирования процессов основана на разделяемых переменных, но с уже выраженными "автоматными" принципами. Ниже примерчик по мотивам тех краёв (Pascal-подобный псевдокод):
Код:
type SomeObj = active object
public
input x: int;
output y: int = 0;
private
shared var s1, s2, s3: int;
procedure proc1;
begin
init;
loop
s1 := comp1(x);
pause;
end;
end;
procedure proc2;
begin
loop
s2 := comp2(x);
pause;
end;
end;
procedure proc3;
begin
pause;
loop
s3 := comp3(s1, s2);
pause;
end;
end;
procedure proc4;
begin
pause;
pause;
loop
y := comp4_1(s3);
pause;
y := comp4_2(s3);
pause;
end;
end;
begin
prepare;
async par([x], [y], [proc1, proc2, proc3, proc4]);
end;
Выше примитивный случай построения конвейерных операций -- чтобы подчеркнуть особенность предметки в виде вычислительных задач по сравнению с задачами логического управления процессами или на основе событий.
"Активный" объект SomeObj имеет вход и выход. В исполняющем теле объекта после некой инициализации prepare запускается оператор "par" -- организация параллельной композиции процессов, в данном случае с т.н. "синхронной" моделью вычислений. Оператор "async" указывает на асинхронность -- каждый процесс (процедура) со своим исполнителем (потоком). Семантика вычислений сохранится, если в реальности будет лишь один исполнитель (или же асинхронность не указана, или определена отдельно для каждого процесса и т.д.).
Такая форма оператора "par" предусматривает в качестве аргументов массив ссылок на input-переменные, массив ссылок на output-переменные, массив процедур/функций. Бесконечно (в цикле) выполняются указанные процессы: при появлении входа реализуется глобальный логический такт автомата -- итерация цикла, заставляющая работать все потоки. После отработки процессов возвращается результат (обновляются output-параметры). Параллельные процессы общаются между собой через разделяемые переменные "shared var" -- нечто вроде volatile-переменных со своей семантикой: процессы манипулируют копиями переменных (при потребности), после каждого такта общие переменные обновляются, возможно с особенностями (к примеру, если несколько процессов изменяют одну и ту же переменную, то можно осуществлять сборку конечного значения через необходимые функции).
Оператор pause есть аля yield -- приостанавливает поток выполнения, указывая на конец локального такта автомата в процессе (когда все процессы завершат свой локальный такт, завершится и глобальный такт с обновлением выхода).
Процесс proc3 начинается с pause -- первый такт автомата пропускается, т.к. переменные s1 и s2 ещё не определены -- задержка исполнения процесса, при этом виртуальный (или реальный) диспетчер переменные s1 и s2 буферизирует для процесса proc3 (диспетчер "понимает" взаимосвязи в потоках данных, shared var и input/output -- виртуальные переменные (как и у Зюбина общие переменные), могут быть и массивы/списки/очереди данных вместо одиночного скалярного значения, внутренняя реализация механизма зависит от потребностей, к примеру, м.б. реализован как массив с политикой кольцевого буфера, и т.д.). Процесс proc4 имеет двойную задержку согласно стадии конвейера, при этом внутри рабочего цикла имеется дополнительная "пауза" -- такты автомата реализуются по-разному в данном случае. Процесс proc4 устанавливает выходной параметр "y". На начальных тактах автомата, где процесс proc4 ещё не работает, для параметра действует значение по умолчанию.
На Р-схемах такой автомат можно отразить так (для Дракон-а, по-видимому, необходимы идентичные принципы, но возникают свои особенности, о чём далее):
Вложение:
SomeObj.png [ 19.83 КБ | Просмотров: 7352 ]
В данном случае "пустая" двойная дуга -- "петля" в графе переходов без действий и условий -- интерпретируется как "пауза" (вместо циклов). Двойные и более "паузы" можно отмечать и иначе (к примеру, как и в тексте аля "pause[2]", так и в графике в виде заголовка дуги как "+[2]====+", где "+" -- вершина).
Оператор "par" задаётся как спецструктура с отмеченной спецвершиной "(||)" -- отражается структура процессов согласно отношениям "производитель-потребитель" (или ярусно-параллельная форма) над общими данными, "двойные" вершины подчёркивают факт наличия асинхронности или собственного исполнителя (вместо прямого доступа к разделяемым переменным для каждого процесса можно задать свои входные/выходные аргументы и при указании параллельной композиции переменные и прочие параметры можно распределить между процессами явно -- второстепенно в данном случае).
Итак, выделяется операция "ожидание" как "pause" -- завершение такта исполнения (или его отсутствие) и ожидание разрешения для следующего такта. В данном случае нет каких-то явных семантических условий по данным для выявления изменений и ожидания этих изменений (хотя таковы возможны косвенно).