Параллелизм, корутины, событийные автоматы,… живая математика

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

    Хотя есть и другой вариант. Не надо ничего пока менять и/или на что-то влиять. Пусть будет многопоточность и корутины, пусть будет… и параллельное автоматное программирование (АП). Пусть соревнуются и, когда это необходимо и возможно, дополняют друг друга. В этом смысле у современного параллелизма есть, как минимум, один плюс — он позволяет это делать.

    Ну, так что, посоревнуемся!?

    1. От последовательных вычислений к параллельным


    Рассмотрим программирование простейшего арифметического уравнения:

    С2 = A + B + A1 + B1; (1)

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

    image
    Рис.1. Структурная модель процессов

    Но структурно схеме на рис.1 соответствует система из трех уравнений:

    С = A + B;
    C1 = A1 + B; (2)
    C2 = C + C1;

    Одновременно (2) это и параллельная реализация уравнения (1), представляющая алгоритм суммирования массива чисел, известный также под именем алгоритма сдваивания. Здесь массив представлен четырьмя числами A, B, A1, B1, переменные С и С1 — промежуточные результаты, а С2 результат — сумма массива.

    image
    Рис.2. Вид диалогов для конфигурации трех параллельных процессов

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

    2. Параллелизм как проблема


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

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

    Пусть имеется два параллельных процесса, которые соответствуют следующей системе уравнений:

    с = a + b; (3)
    a = b + c;

    Предположим, переменным a, b, c присвоены начальные значения 1, 1, 0. Можно ожидать, что протокол вычислений для пяти шагов будет следующим:

    a              b               c
    1.000       1.000       0.000      
    1.000       1.000       2.000       
    3.000       1.000       2.000       
    3.000       1.000       4.000       
    5.000       1.000       4.000        
    5.000       1.000       6.000   

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

    Но, скорее всего, вы получите примерно такой протокол:

       a              b               c
    1.000       1.000       0.000      
    3.000       1.000       2.000       
    5.000       1.000       4.000       
    7.000       1.000       6.000       
    9.000       1.000       8.000       
    11.000     1.000     10.000       
    

    Он эквивалентен работе процесса, выполняющему в цикле два последовательных оператора:

    с = a + b; a = b + c; (4)

    Но может случиться, что исполнение операторов будет ровно обратным, и тогда протокол будет следующим:

       a              b               c
    1.000       1.000       0.000      
    1.000       1.000       2.000       
    3.000       1.000       4.000       
    5.000       1.000       6.000       
    7.000       1.000       8.000       
    9.000       1.000     10.000       
    

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

    В рамках технологии АП работа с общими переменными процессов разрешается просто и корректно. Здесь чаще всего не требуется особых усилий на синхронизацию процессов и работу с данными. Но нужно будет выделить действия, которые будут считаться условно мгновенными и неделимыми, а также создать автоматные модели процессов. В нашем случае действиями будут операторы суммирования, а за их запуск будут отвечать автоматы с циклическими переходами.
    Листинг 1 демонстрирует код процесса, реализующего операцию суммирования. Его моделью является конечный автомат (см. рис. 3) с одним состоянием и безусловным переходом в форме петли, у которого единственное действие y1, выполнив операцию суммирования двух переменных, помещает результат в третью.

    image
    Рис.3. Автоматная модель операции суммирования

    Листинг 1. Реализация автоматного процесса для операции суммирования
    #include "lfsaappl.h"
    class FSumABC :
        public LFsaAppl
    {
    public:
        LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FSumABC(nameFsa); }
        bool FCreationOfLinksForVariables();
        FSumABC(string strNam);
        CVar *pVarA;		//
        CVar *pVarB;		//
        CVar *pVarC;		//
        CVar *pVarStrNameA;        //
        CVar *pVarStrNameB;        //
        CVar *pVarStrNameC;        //
    protected:
        void y1();
    };
    
    #include "stdafx.h"
    #include "FSumABC.h"
    
    static LArc TBL_SumABC[] = {
        LArc("s1",	"s1","--", "y1"),
        LArc()
    };
    
    FSumABC::FSumABC(string strNam):
        LFsaAppl(TBL_SumABC, strNam, nullptr, nullptr)
    { }
    
    bool FSumABC::FCreationOfLinksForVariables() {
        pVarA = CreateLocVar("a", CLocVar::vtBool, "variable a");
        pVarB = CreateLocVar("b", CLocVar::vtBool, "variable c");
        pVarC = CreateLocVar("c", CLocVar::vtBool, "variable c");
        pVarStrNameA = CreateLocVar("strNameA", CLocVar::vtString, "");
        string str = pVarStrNameA->strGetDataSrc();
        if (str != "") { pVarA = pTAppCore->GetAddressVar(pVarStrNameA->strGetDataSrc().c_str(), this); }
        pVarStrNameB = CreateLocVar("strNameB", CLocVar::vtString, "");
        str = pVarStrNameB->strGetDataSrc();
        if (str != "") { pVarB = pTAppCore->GetAddressVar(pVarStrNameB->strGetDataSrc().c_str(), this); }
        pVarStrNameC = CreateLocVar("strNameC", CLocVar::vtString, "");
        str = pVarStrNameC->strGetDataSrc();
        if (str != "") { pVarC = pTAppCore->GetAddressVar(pVarStrNameC->strGetDataSrc().c_str(), this); }
        return true;
    }
    
    void FSumABC::y1() { 
        pVarC->SetDataSrc(this, pVarA->GetDataSrc() + pVarB->GetDataSrc()); 
    }
    


    Важным, а точнее, даже необходимым, здесь является использование переменных среды ВКПа. Их «теневые свойства» обеспечивают корректное взаимодействие процессов. Более того, среда позволяет изменить режим своей работы, исключив запись переменных в промежуточную теневую память. Анализ протоколов, полученных в этом режиме, позволяет убедиться в необходимости использования теневых переменных.

    3. А корутины?...


    Хорошо бы знать, как с поставленным заданием справятся корутины, представленные языком Kotlin. Возьмем в качестве шаблона решения программу, рассмотренную при обсуждении статьи [1]. Она имеет структуру, которая легко приводится к необходимому виду. Для этого заменим в ней логические переменные на числовой тип и добавим еще одну переменную, а вместо логических операций будем использовать операции суммирования. Соответствующий код приведен в листинге 2.

    Листинг 2. Программа параллельного суммирования на языке Kotlin
    import kotlinx.coroutines.*
    
    suspend fun main() =
        // Structured concurrency: if any child coroutine fails,
        // everything else will be cancelled
        coroutineScope {
            var a = 1
            var b = 1
            var c = 0;
            // Use default thread pool 
            withContext(Dispatchers.Default) {
                for (i in 0..4) {
                   var res = listOf(async { a+b }, async{ b+c }).map { it.await() }
    			c = res[0]
    			a = res[1]
                   println("$a, $b, $c")
                }
            }
        }
    


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

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

    listOf(async { с = a+b }, async{ а = b+c })
    

    Как показало тестирование (это можно сделать в online-режиме на сайте Kotlin — kotlinlang.org/#try-kotlin), его применение приводит к совершенно непредсказуемому результату, который к тому же меняется от запуска к запуску. И лишь более внимательный анализ исходной программы привел к правильному коду.

    Код, который содержит ошибку с точки зрения функционирования программы, но легитимен с точки зрения языка, заставляет опасаться за надежность программ на нем. Это мнение, наверное, может быть оспорено знатоками языка Kotlin. Тем не менее, легкость совершения ошибки, которую нельзя объяснить только лишь недостаточным пониманием «корутинного программирования», к подобным выводам все же настойчиво подталкивает.

    4. Событийные автоматы в Qt


    Ранее мы установили, что событийный автомат — это не автомат в классическом его определении. Хорош он или плох, но в какой-то мере событийный автомат все же родственник классических автоматов. Дальний ли, ближний, но говорить об этом надо прямо, чтобы не возникало заблуждений на этот счет. Об этом мы начали разговор в [2], но все за то, чтобы его продолжить. Сейчас мы это и сделаем, рассмотрев другие примеры использования событийных автоматов в Qt.

    Безусловно, событийный автомат можно рассматривать как вырожденный случай классического автомата с неопределенной и/или переменной длительностью такта, привязанного к событию. Возможность подобной интерпретации показана в предыдущей статье при решении только лишь одного и, притом, достаточно специфического примера (см. подробнее [2]). Далее мы этот пробел постараемся устранить.

    Библиотека Qt связывает переходы автомата только лишь с событиями, что является серьезным ограничением. Например, в том же языке UML переход ассоциируется не только с событием, названным инициирующим событием, но и с защитным условием — логическим выражением, вычисляемым после получения события [3]. В MATLAB ситуация смягчена больше и звучит так: «если имя события не указано, то переход произойдет при наступлении любого события» [4]. Но и там и там первопричиной запуска перехода остается событие/события. А как быть, если событий нет?

    Если событий нет, то… их можно попробовать создать. Листинг 3 и Рис. 4 демонстрируют, как это сделать, используя в качестве «обертки» событийного Qt-класса потомок автоматного класса LFsaAppl среды ВКПа. Здесь действие y2 с периодичностью дискретного времени автоматного пространства посылает сигнал, инициирующий запуск перехода Qt-шного автомата. Последний с помощью метода s0Exited запускает действие y1, реализующее операцию суммирования. Заметим, что событийный автомат создается действием y3 строго после проверки инициализации локальных переменных класса LFsaAppl.

    image
    Рис.4. Комбинация классического и событийного автоматов

    Листинг 3. Реализация модели суммирования событийным автоматом
    #include "lfsaappl.h"
    class QStateMachine;
    class QState;
    
    class FSumABC :
        public QObject,
        public LFsaAppl
    {
        Q_OBJECT
    ...
    protected:
        int x1();
        void y1(); void y2(); void y3(); void y12();
    signals:
        void GoState();
    private slots:
        void s0Exited();
    private:
        QStateMachine * machine;
        QState * s0;
    };
    
    #include "stdafx.h"
    #include "FSumABC.h"
    #include <QStateMachine>
    #include <QState>
    
    static LArc TBL_SumABC[] = {
        LArc("st",	"st","^x1",	"y12"),
        LArc("st",	"s1","x1",	"y3"),
        LArc("s1",	"s1","--",	"y2"),
        LArc()
    };
    
    FSumABC::FSumABC(string strNam):
        QObject(),
        LFsaAppl(TBL_SumABC, strNam, nullptr, nullptr)
    { }
    ...
    int FSumABC::x1() { return pVarA&&pVarB&&pVarC; }
    
    void FSumABC::y1() {
        pVarC->SetDataSrc(this, pVarA->GetDataSrc() + pVarB->GetDataSrc());
    }
    //
    void FSumABC::y2() { emit GoState(); }
    //
    void FSumABC::y3() {
        s0 = new QState();
        QSignalTransition *ps = s0->addTransition(this, SIGNAL(GoState()), s0);
        connect (s0, SIGNAL(entered()), this, SLOT(s0Exited()));
        machine = new QStateMachine(nullptr);
        machine->addState(s0);
        machine->setInitialState(s0);
        machine->start();
    }
    // инициировать ссылки на операнды
    void FSumABC::y12() { FInit(); }
    
    void FSumABC::s0Exited() { y1(); }
    


    Выше мы реализовали совсем простой автомат. Или, что точнее, комбинацию классического и событийного автоматов. Если предыдущую реализацию класса FSumABC заменить на созданную, то отличий в работе приложения просто не будет. Но для более сложных моделей ограниченные свойства событийных автоматов начинают проявляться в полной мере. Как минимум, уже в процессе создания модели. На листинге 4 представлена реализация модели элемента И-НЕ в форме событийного автомата (подробнее об используемой автоматной модели элемента И-НЕ см. [2]).

    Листинг 4. Реализация модели элемента И-НЕ событийным автоматом
    #include <QObject>
    
    class QStateMachine;
    class QState;
    
    class MainWindow;
    class ine : public QObject
    {
        Q_OBJECT
    public:
        explicit ine(MainWindow *parent = nullptr);
        bool bX1, bX2, bY;
    signals:
        void GoS0();
        void GoS1();
    private slots:
        void s1Exited();
        void s0Exited();
    private:
        QStateMachine * machine;
        QState * s0;
        QState * s1;
        MainWindow *pMain{nullptr};
    friend class MainWindow;
    };
    
    #include "ine.h"
    #include <QStateMachine>
    #include <QState>
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    ine::ine(MainWindow *parent) :
        QObject(parent)
    {
        pMain = parent;
        s0 = new QState();
        s1 = new QState();
    
        s0->addTransition(this, SIGNAL(GoS1()), s1);
        s1->addTransition(this, SIGNAL(GoS0()), s0);
    
        connect (s0, SIGNAL(entered()), this, SLOT(s0Exited()));
        connect (s1, SIGNAL(entered()), this, SLOT(s1Exited()));
    
    
        machine = new QStateMachine(nullptr);
        machine->addState(s0);
        machine->addState(s1);
        machine->setInitialState(s1);
        machine->start();
    }
    
    void ine::s1Exited() {
        bY = !(bX1&&bX2);
        pMain->ui->checkBoxY->setChecked(bY);
    }
    
    void ine::s0Exited() {
        bY = !(bX1&&bX2);
        pMain->ui->checkBoxY->setChecked(bY);
    }
    


    Становится понятно, что событийные автоматы в Qt базируются строго на автоматах Мура. Это ограничивает возможности и гибкость модели, т.к. действия связываются только с состояниями. В результате, например, невозможно различить два перехода из состояния 0 в 1 для автомата, приведенного на рис. 4 в [2].

    Безусловно, для реализации автоматов Мили можно использовать известную процедуру перехода к автоматам Мура. Но это приводит к увеличению числа состояний и устраняет простую, наглядную и полезную ассоциацию состояний модели с результатом ее действий. К примеру, после подобных преобразований единичному состоянию выхода 1 модели из [2] необходимо сопоставить уже два состояния автомата Мура.

    На более сложной модели с очевидностью начинают проявляться проблемы с условиями переходов. Чтобы их обойти для рассматриваемой программной реализации элемента И-НЕ анализ состояния входных каналов был встроен в диалог управления моделью, что и демонстрирует листинг 5.

    Листинг 5. Диалог управления элементом И-НЕ
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "ine.h"
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        pine = new ine(this);
        connect(this, SIGNAL(GoState0()), pine, SIGNAL(GoS0()));
        connect(this, SIGNAL(GoState1()), pine, SIGNAL(GoS1()));
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    
    void MainWindow::on_checkBoxX1_clicked(bool checked)
    {
        bX1 = checked;
        pine->bX1 = bX1;
        bY = !(bX1&&bX2);
        if (!(bX1&&bX2)) emit GoState0();
        else emit GoState1();
    }
    
    void MainWindow::on_checkBoxX2_clicked(bool checked)
    {
        bX2 = checked;
        pine->bX2 = bX2;
        bY = !(bX1&&bX2);
        if (!(bX1&&bX2)) emit GoState0();
        else emit GoState1();
    }
    


    В сумме все отмеченное явно усложняет модель и создает проблемы с пониманием ее работы. Кроме того, подобные локальные решения могут не работать при рассмотрении композиций из них. Хорошим примером в данном случае может быть попытка создания модели RS-триггера (подробнее о двухкомпонентной автоматной модели RS-триггера см. [5]). Однако удовольствие от достигнутого результата доставим испытать поклонникам событийных автоматов. Если… если, конечно, это у них получится ;)

    5. Квадратичная функция и … бабочка?


    Удобно изменение входных данных представлять как внешний параллельный процесс. Выше это был диалог управления моделью элемента И-НЕ. При этом скорость изменения данных может заметно повлиять на результат. Для подтверждения этого рассмотрим вычисление квадратичной функции y = ax² + bx + с, которую реализуем в форме множества параллельно функционирующих и взаимодействующих между собой блоков.

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

    Итак, добавим к блоку суммирования блоки умножения, деления и возведение в степень. Имея такой набор, мы сможем «собирать» математические выражения любой сложности. Но мы можем иметь также «кубики», реализующие более сложные функции. На рис.5. показана реализация квадратичной функции в двух вариантах – многоблочном (см. стрелку с меткой – 1, а также рис. 6) и варианте из одного блока (на рис. 5 стрелка с меткой 2).

    image
    Рис.5. Два варианта реализации квадратичной функции

    image
    Рис.6. Структурная модель вычисления квадратичной функции

    То, что на рис.5. кажется «размытым» (см. стрелку с меткой 3), при надлежащем увеличении (см. графики (тренды), помеченные 4) убеждает, что дело не в свойствах графики. Это результат влияния времени на вычисление переменных: переменной y1 – выходного значения многоблочного варианта (красный цвет графика) и переменной y2 – выходного значения одноблочного варианта (черный цвет). Но и тот и другой графики отличаются от «абстрактного графика» [y2(t), x(t-1)] (зеленый цвет). Последний построен для значения переменной y2 и задержанного на один такт значения входной переменной (см. переменную с именем x[t-1]).

    Таким образом, чем выше скорость изменения входной функции x(t), тем сильнее будет «эффект размывания» и тем дальше будут отстоять графики y1, y2 от графика [y2(t), x(t-1)]. Обнаруженный «дефект» можно использовать в своих целях. Например, нам ни что не мешает подать на вход синусоидальный сигнал. Мы рассмотрим даже более сложный вариант, когда аналогично будет меняться также первый коэффициент уравнения. Результат эксперимента демонстрирует скрин среды ВКПа, показанный на рис. 7.

    image
    Рис.7. Результаты моделирования при синусоидальном входном сигнале

    На скрине слева внизу показан сигнал, подаваемый на входы реализаций квадратичной функции. Выше него – графики выходных значений y1 и y2. Графики в виде «крылышек» — значения, построенные в двух координатах. Так, с помощью различных реализаций квадратичной функции мы нарисовали половинку «бабочки». Нарисовать целую – дело техники…

    Но парадоксы параллелизма на этом не заканчиваются. На рис. 8 приведены тренды при «обратном» изменении независимой переменной x. Они проходят уже левее «абстрактного» графика (последний, отметим, не изменил своего положения!).

    image
    Рис. 8. Вид графиков при прямом и обратном линейном изменении входного сигнала

    На данном примере становится очевидной «двойная» погрешность выходного сигнала по отношению к его «мгновенному» значению. И чем медленнее будет вычислительная система или выше частота сигнала, тем больше будет погрешность. Синусоидальный сигнал — пример прямого и обратного изменения входного сигнала. Именно в силу этого «крылышки» на рис.4 приняли такой вид. Без эффекта «обратной погрешности» они были бы в два раза уже.

    6. Адаптивный ПИД-регулятор


    Рассмотрим еще один пример, на котором проявляются рассмотренные проблемы. На рис. 9 приведена конфигурация среды ВКП(а) при моделировании адаптивного ПИД-регулятора. Приведена и структурная схема, на которой ПИД-регулятор представлен блоком с именем PID. На уровне «черного ящика» он аналогичен рассмотренной ранее одноблочной реализации квадратичной функции.

    Результат сравнения результатов расчета модели ПИД-регулятора в рамках некоторого математического пакета и протокола, полученного по результатам моделирования в среде ВКП(а), представлен на рис.10, где красный график – расчетные значения, а синий — протокол. Причина их несовпадения в том, что расчет в рамках математического пакета, как показал дальнейший анализ, соответствует последовательной работе объектов, когда сначала отрабатывает ПИД-регулятор и останавливается, а затем – модель объекта и т.д. в цикле. Среда же ВКПа реализует/моделирует параллельную работу объектов соответственно реальной ситуации, когда модель и объект работают параллельно.

    image
    Рис.9. Реализация ПИД-регулятора

    image
    Рис.10. Сравнение расчетных значений с результатами моделирования ПИД-регулятора

    Поскольку, как мы уже анонсировали, в ВКП(а) имеется режим имитации последовательной работы структурных блоков, то проверить гипотезу о последовательном режиме расчета ПИД-регулятора не составляет труда. Изменив режим работы среды на последовательный, мы получим совпадение графиков, что и демонстрирует рис.11.

    image
    Рис.11. Последовательный режим работы ПИД-регулятора и объекта управления

    7. Выводы


    В рамках ВКП(а) вычислительная модель наделяет программы свойствами, характерными для реальных «живых» объектов. Отсюда и образная ассоциация с «живой математикой». Как показывает практика, мы и в моделях просто обязаны учитывать то, что нельзя игнорировать в реальной жизни. В параллельных вычислениях это, прежде всего, время и конечность вычислений. Конечно, не забывая при этом об адекватности [автоматной] математической модели тому или иному «живому» объекту.

    Невозможно бороться с тем, что нельзя победить. Речь о времени. Такое возможно разве что только в сказке. Но есть смысл в том, чтобы его учитывать и/или даже использовать в своих целях. В современном параллельном программировании игнорирование времени приводит к множеству сложно контролируемых и выявляемых проблем — гонкам сигналов, тупикам процессов, проблемам с синхронизацией и т.д. и т.п. Технология ВКП(а) во многом свободна от подобных проблем просто потому, что включает модель реального времени и учитывает свойство конечности вычислительных процессов. Она содержит то, что ее аналоги в своем большинстве просто-напросто игнорируют.

    И в заключение. Бабочки бабочками, но можно, к примеру, рассмотреть систему уравнений из квадратичной и линейной функций. Для этого достаточно к уже созданной модели добавить модель линейной функции и процесс, контролирующий их совпадение. Так решение будет найдено путем моделирования. Оно будет, скорее всего, не столь точным, как аналитическое, но получено проще и быстрее. А во многих случаях этого более чем достаточно. Да и нахождение аналитического решения — это, как правило, часто открытый вопрос.
    В связи с последним вспомнились АВМ. Для тех, кто не в курсе или подзабыл, — аналоговые вычислительные машины. Структурные принципы во много общие, да и подход к нахождению решения.

    Приложение


    1) Видео: youtu.be/vf9gNBAmOWQ

    2) Архив примеров: github.com/lvs628/FsaHabr/blob/master/FsaHabr.zip.

    3) Архив необходимых dll-библиотек Qt версии 5.11.2: github.com/lvs628/FsaHabr/blob/master/QtDLLs.zip

    Примеры разработаны в среде Windows 7. Для их установки раскрыть архив примеров и, если у вас не установлен Qt или текущая версия Qt отлична от версии 5.11.2, то дополнительно раскрыть также архив Qt и прописать путь к библиотекам в переменную среды Path. Далее запустить \FsaHabr\VCPaMain\release\FsaHabr.exe и с помощью диалога выбрать каталог конфигурации того или иного примера, например, \FsaHabr\9.ParallelOperators\Heading1\Pict1.C2=A+B+A1+B1\ (см. также видео).

    Замечание. При первом запуске вместо диалога выбора каталога может появиться диалог выбора файла. Также выбираем каталог конфигурации и в нем какой-нибудь файл, например, vSetting.txt. Если вообще не появляется диалог выбора конфигурации, то перед запуском удалить файл ConfigFsaHabr.txt в каталоге, где находится файл FsaHabr.exe.

    Чтобы не повторять выбор конфигурации в диалоге «ядро: автоматные пространства» (его можно открыть с помощью пункта меню: FSA-инструменты/Управление/Управление Пространствами), нажать кнопку «запомнить путь к каталогу» и снять выбор «вывести диалог выбора конфигурации при запуске». В дальнейшем для выбора другой конфигурации этот выбор необходимо будет установить вновь.

    Литература


    1. ЯПФ, конвейер, автоматные вычисления и опять… корутины. [Электронный ресурс], Режим доступа: habr.com/ru/post/488808 свободный. Яз. рус. (дата обращения 22.02.2020).
    2. Автоматы — вещь событийная? [Электронный ресурс], Режим доступа: habr.com/ru/post/483610 свободный. Яз. рус. (дата обращения 22.02.2020).
    3. БУЧ Г., РАМБО ДЖ., ЯКОБСОН И. Язык UML. Руководство пользователя. Второе издание. Академия АЙТИ: Москва, 2007. – 493 с.
    4. Рогачев Г.Н. Stateflow V5. Руководство пользователя. [Электронный ресурс], Режим доступа: bourabai.kz/cm/stateflow.htm свободный. Яз. рус. (дата обращения 10.04.2020).
    5. Модель параллельных вычислений. [Электронный ресурс], Режим доступа: habr.com/ru/post/486622 свободный. Яз. рус. (дата обращения 11.04.2020).
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 47

      0

      Товарищи… Кто осилил? О чем статья? Что такое ВКП?

        0

        Всесоюзная коммунистическая партия (бАльшевиков) ))

          0
          Почти что так :)
          Но все же более точная расшифровка в моих предыдущих статьях все на эту же тему — тему автоматного программирования (АП). Но для ленивых не поленюсь повторить — Визуально-Компонентное Программирование (автоматное). Отсюда сокращенно и «родилось» ВКП(а). Хотя имелось в виду и ВКПб :)
        0
        К вопросу о чем статья и ее смыслах.
        Статья о параллельном программировании
        1. Статья о том, как из маленьких кирпичиков — программных объектов сложить прекрасное здание — параллельную программу. А если эти кирпичики «живые», то вдохнуть в него еще и «душу».
        2. Статья о том, что даже в случае, когда мы знаем досконально поведение простейших элементов, что при этом выкинет «душа» нам чаще всего не ведомо.
        3. Статья о том, что если поведение «души» поначалу совсем не ведомо, это не означает, что оно должно быть совсем непредсказуемым.
        4. Статья о том, что неведомое поведение при одних и тех же начальных условиях должно быть повторяемо и в этом и только в этом случае оно поддается изучению и анализу.
        5. Статья не о том, как из огромного куска гранита, отсекая лишнее, создать шедевр, а том, как, имея даже неказистые кирпичи или камни, сложить красивое и полезное здание, как, имея ограниченный набор красок, нарисовать картину, как, имея битые кусочки разноцветного стекла, сложить мозаику, как, имея простое поведение, создать сложное.
        6. Статья о том, как элементарное поведение отдельных объектов позволяет, не напрягаясь интеллектуально, а лишь простыми структурными методами решить фактически любую сложную проблему.
        7. Статья-предупреждение о том, что любая программная формализация не может быть оторвана от жизненных реалий (уравнение типа у=f(x) в корне не верно, т.к. нет (!) мгновенных вычислений). В противном случае не найти простого структурного решения даже для простейших задач/проблем.

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

        Или, если уж совсем кратко. Данная статья про автоматное программирование (АП), о примерах его реализации в среде Визуально-Компонентного Программирования (автоматного) — ВКП(а). Статья с кратким, почти мимолетным упоминанием других подходов (речь о корутинах и событийных автоматах). С примерами аналогичного их применения и/или призывами аналогичные проблемы решить.
          0

          Понятнее не стало...


          Я тут философски могу сказать. Если я не могу ребёнку рассказать, чем я занимаюсь, значит я не понимаю того, чем занимаюсь. (Не помню откуда цитата).


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


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

            0

            Не обращайте внимания, эта статья написана для нас, апологетов корутин, с призывом вернуться к корням и писать автоматы руками, а не генерировать компилятором.

              0
              И это, действительно, так.
              Но, предположим, Вы правы.
              Тогда, наверное, теорию автоматов можно заменить на теорию корутин? И начала Кибернетики переписать? Глушков, фон Нейман и иже с ними устарели и формально и морально?
              Интересно, а как это будет выглядеть? Я, если честно, не могу это себе представить. От слова — никак.
              Хотя, не скрою, было бы любопытно посмотреть, почитать, сравнить и тогда где-то меня даже… «понять и простить» ;)
                0

                Хотя бы не пытаться натянуть сову на глобус. А именно, пытаться объяснить с помощью автоматной модели корутины.


                Для начала ответьте мне на вопрос — для решения какой задачи понадобились корутины? Почему, внезапно, куча языков их стала поддерживать? Какие альтернативные варианты решения этой есть? Автоматы. А ещё? Какие у всех плюсы, какие минусы?


                Думаю, уже в процессе поиска ответа (в сети Интернет, разумеется) на первый вопрос вы поймете ответ на второй. А третий уже покажет вам, что не покрывается автоматной моделью.

            0
            Ну и сам Тьюринг ушел в синергетику.
            “Компьютеры только тогда будут вести себя интересным способом и проявлять несомненную разумность, когда им позволять совершать ошибки.” А. Тьюринг
              0
              Интересным — возможно, но разумность — не совершать ошибки. Ну, как минимум, их исправлять ;)
            0
            Код, который содержит ошибку с точки зрения функционирования программы, но легитимен с точки зрения языка, заставляет опасаться за надежность программ на нем. Это мнение, наверное, может быть оспорено знатоками языка Kotlin.

            Я готов оспорить это утверждение для (почти) любого языка программирования. Следите за руками:


            1/0

            И да, замена


            var res = listOf(async { a+b }, async{ b+c }).map { it.await() }
            c = res[0]
            a = res[1]

            на


            listOf(async { с = a+b }, async{ а = b+c })

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


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


            Что я нахожу ироничным, так это то, что вы переизобрели продолжения (continuation), которые были в лиспе с незапамятных времен. Смотрите:


            class FSumABC :
                public LFsaAppl
            {
            public:
                // Эти две функции можно объединить
                LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FSumABC(nameFsa); }
                bool FCreationOfLinksForVariables();
                // Тривиальный конструктор
                FSumABC(string strNam);
                // Сохраненные переменные
                CVar *pVarA;        //
                CVar *pVarB;        //
                CVar *pVarC;        //
                CVar *pVarStrNameA;        //
                CVar *pVarStrNameB;        //
                CVar *pVarStrNameC;        //
            protected:
                // Функция вычисления
                void y1();
            };

            Сравните с тем, что генерирует котлиновский компилятор для suspend лямбды (из внутренней документации по кодогенерации корутин)


            1. supertypes: `kotlin/coroutines/jvm/internal/SuspendLambda` and `kotlin/jvm/functions/Function{N}`
            1. package-private captured variables
            1. private label field of int. Private, since it is used only in the lambda itself.
            1. private parameter fields. The reason of visibility is the same, as for `label` field.
            1. private fields for spilled variables. Same.
            1. public final method `invokeSuspend` of type `(Ljava/lang/Object;)Ljava/lang/Object;`.
            It overrides `BaseContinuationImpl`'s `invokeSuspend`.
            1. public final `create` of type `(<params>,Lkotlin/coroutines/Continuation)Lkotlin/coroutines/Continuation`.
            `<params>` types are erased. In other words, their types are `Ljava/lang/Object;` as long as the number of parameters
            is either 0 or 1. This is because the method overrides base class's `create`.
            1. public final `invoke` of type `(<params>,Ljava/lang/Object;)Ljava/lang/Object;`. Since it overrides `Function{N}`'s
            `invoke`, types of `<params>` are erased.
            1. public or package-private constructor: `<init>` of type `(<captured-variables>,Lkotlin/coroutines/Continuation;)V`,
            where we call `SuspendLambda`'s constructor with arity and completion and initlialize captured variables.
            The arity is known at compile-time, but the completion is provided as argument to the constructor.

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


            CVar* ... -> захваченные переменные, параметры и сохраненные локальные переменные
            LFsaAppl -> SuspendLambda
            y1 -> invokeSuspend
            Create и FCreationOfLinksForVariables -> create
            FSumABC -> <init>, конструктор

            Есть ещё функция invoke, которая позволяет вызвать корутину как функцию.


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

              0
              Следите за руками

              «Наши руки не для скуки...» ;) Это сложно, но попробую.

              Рассмотрим семантику, но сначала о… синтаксисе.
              Мне совсем не понятен (ну, почти) такой… a+b и b+c. Что это?
              И откуда вдруг c = res[0] и a = res[1]? И почему так, а не в другом порядке?
              «Руки» так решили?
              Конечно, можно гадать и в конце концов догадаться. Что, кстати и произошло ;)
              Но на мой взгляд нужно быть, во-первых, честнее, а, во-вторых, не давать возможности рукам «шалить».
              В данном случае мне понятно, когда так:
              c=a+b
              a=b+c
              Обращаю внимание на синтаксис и семантику.
              Не так: c=a+b; a=b+c; и не наоборот. А именно так, как выше.
              Но и в этом не вся правда, а есть некое лукавство.
              Мы понимаем, что это два параллельных процесса суммирования и даже согласны с тем, что они имеют общие данные (это следует из их имен).
              Но давайте уж до конца добивать… Что это за процесс, например, c=a+b. Как он начинается, как завершается и завешается ли? Гадать и следить за «руками»?

              Автоматы в этом смысле не дают простора «ручкам» (см. автоматную реализацию): есть два процесса (они однозначно представлены автоматными моделями), входы и выход которых связан с определенными переменными. Поскольку эти переменные используются для связи между процессами, то они обязательно имеют «теневой признак» (те, что используются внутри процессов, могут быть обычными).
              Таким образом, автоматная реализация однозначна в своей интерпретации и, как бы «ручки» не мечтали, результат работы процессов будет один и только один (см. первый протокол статьи). И здесь можно обсуждать синтаксис, но семантика должна быть именно такая.

              С корутинами ситуация иная. И здесь дело даже не в синтаксисе, а именно в семантике, которая достаточно многозначна: можно написать и так и так. И с точки зрения синтаксиса и то и другое будет корректно, но семантика, т.е. смысл, как и результат, будет разный. Как с этим быть? «Умище-то, умище-то куда деть»?
              Мне сложно судить, что я переизобрел, но то, что Вы используете схему (подход) для реализации автоматов, которая давно известна в теории синтеза цифровых схем, это для меня очевидно.
              Просто корутины к процессу реализации, пардон, процессов заходят, так сказать, с другой стороны. Т.е. используют, насколько можно судить, автоматы для реализации «схемы». А в теории автоматов сначала создается автоматная модель, а «схема» затем используется для их синтеза (сиречь, — реализации). Если бы это было по-другому, то в теории автоматов вообще не было бы смысла. Совсем. Иначе зачем эти странные «яйцеголовые» их рисуют ручками, когда «проще» сразу рисовать «схемы»?
              И, кстати, во времена, когда мы еще кроме программирования могли разрабатывать и схемы, у меня были друзья-электронщики, которые напрочь презирали теорию автоматов и синтеза цифровых схем, а божились, что разработают любую схему без какой-либо теории. Но… как потом оказалось, это «проходит» только для случая достаточно простой логики (алгоритма) функционирования схемы.

                0

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


                И откуда вдруг c = res[0] и a = res[1]? И почему так, а не в другом порядке?

                Давайте я переформулирую задачу, сделав её более абстрактной, и, надеюсь, более понятной для вас. Заменим вычисления a+b и b+c на F(a,b) и G(b,c). Пусть теперь количество тактов, которое занимают F и G неизвестно заранее. Препишите автомат так, чтобы он не зависел от таймингов F и G.


                Усложним задачу. Смоделируем теперь соединение по сети. Пусть функция F с очень малой вероятностью кидает исключение (используем встроеный в язык Си++ механизм сообщения об отказе). Так мы смоделировали внезапное отключение от сети. Перепишите теперь весь граф автоматов так, чтобы он аварийно завершил работу. Ах да, забыл, ваша модель предполагает, что мир, в котором исполняется программа — идеален, что исключительных ситуаций не бывает, что "лучше не допускать ошибок".


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


                И, кстати, во времена, когда мы еще кроме программирования могли разрабатывать и схемы, у меня были друзья-электронщики, которые напрочь презирали теорию автоматов и синтеза цифровых схем, а божились, что разработают любую схему без какой-либо теории. Но… как потом оказалось, это «проходит» только для случая достаточно простой логики (алгоритма) функционирования схемы.

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


                Ну серьёзно, что это за модель, которая не моделирует реальный мир с его отказами! И автор ещё заявляет о "жизненных реалиях"...

                  0
                  ЕМНИП… ваша модель не покрывает корутины

                  Могу в ответ ЕМНИП-уть. Я уже говорил, что автоматная модель вычислений, как и модель Тьюринга, «покрывает» любую модель. Это доказано тем же Тьюрингом. А вот про корутины я про какое-то аналогичное доказательство не слышал. Более того, корутины это даже не алгоритмическая модель. Это в меру моего скромного понимания ;)
                  Препишите автомат так, чтобы он не зависел от таймингов F и G
                  Ну, во-первых, надо все же говорить о функциях возвращающих значение, т.е. с = F(a,b) и а = G(b,c). А, во-вторых, о каких таймингах идет речь? Автоматы (суммирования) останутся автоматами при любом «тайминге». Тут, если честно, я не понял в чем состоит сама проблема?
                  … Ах да, забыл, ваша модель предполагает, что мир, в котором исполняется программа — идеален, что исключительных ситуаций не бывает, что «лучше не допускать ошибок».

                  Я работаю по большей части с железом, которое не менее «реально», чем работа с базами данных, сетью и т.д. и т.п. Если предполагаются какие-то проблемы, а они, конечно, предполагаются, например, разрыв сети, отказ датчика, отсутствие отклика порта и т.д. и т.п. Все это закладывается в модель. Например, засекаем время ожидания ответа. Если нет — реагируем на ситуацию. Но ни какой паники: корректно и аккуратно отходим на исходные позиции, оповещая об этом тех, кому это надо знать. :).
                  Спасибо за пример в пользу моей точки зрения.

                  Наоборот, — в пику ;)
                  Никто не мешает мне менять связи между ними по своему усмотрению, опять же, во время исполнения…
                  Тоже мне проблемы, напугали… ;) Попробую (только чуть позже) все это продемонстрировать на примере так называемой «задачи о жуках». Есть готовая демка, где они преследуют друг друга, погибают, если их пожирают при этом, рождаются и т.д. и т.п. Нужно только сделать видео.
                  Ну серьёзно, что это за модель, которая не моделирует реальный мир с его отказами! И автор ещё заявляет о «жизненных реалиях»...
                  и я серьезно… Если модель универсальна, то она моделирует все, что Вы только можете себе представить. В том числе и любые отказы и откаты. За обоснованием опять же отсылаю к Тьюрингу, который Алан. А на практике что только не приходилось «обрабатывать». И как-то все работало и работает. Без каких-либо «исключений». Отказы обрабатываются, откаты совершаются… Особенно почему-то помнится достала когда-то давно реализация протокола КАМАК. Вот где было накручено с этими самыми отказами! А уж железо глючило — мрак!
                    0
                    1. "автоматная модель не включает в себя обработку ошибок" (https://habr.com/ru/post/489664/#comment_21319926).
                    2. "Если модель универсальна, то она моделирует все, что Вы только можете себе представить. В том числе и любые отказы и откаты."

                    Их этих двух утверждений следует, что автоматная модель не универсальна. QED. И как бы вы не выкручивались, без кода отмены всего графа автоматов (structured concurrency) ваши слова — пустой звук. Ваши заявления требуют доказательств, тогда как я свои предоставил.


                    И так как вы, к тому же, не ответили ни на один из моих вопросов, касательно отмены графа автоматов, причины популярности корутин, я, с вашего позволения, исключаю себя из беседы. Продолжайте заниматься теперь уже монологом. Мне, при всём моём терпении, надоело спорить с чат-ботом.

                      0
                      я, с вашего позволения, исключаю себя из беседы

                      Обидно, досадно, но… я надеюсь, что Вы еще передумаете ;)
                      Итак, ближе «к телу»…
                      По поводу жуков см. статью. Обратите внимание на скриншоты. В ней все описано и, даже не знаю, надо ли теперь видно?
                      Кстати, на сайте SoftCraft в разделе КА-технология много чего на тему того, что теперь я называю ВКП(а) (иногда короче — ВКПа). Есть решение «задачи о стрелках», философы Дейкстры. Все это классика параллельного программирования. Решение подобных задач просто обязательно на «территории» параллельного программирования. Уж коли корутины на нее вступили, то необходимо решение и от них.
                      Еще в тему. Тему имитационного моделирования систем. Ее реализует среда AnyLogic. На эту тему есть книга Карпова Ю. Имитационное моделирование систем. Введение в моделирование с AnuLogic 5. СПб. 2005. В ней рассматриваются и стрелки и жуки (книгу в Инете можно найти). Все это хорошие примеры применения/приложения автоматов. И именно в практике. Подобных разработок и на корутинах не знаю. Но, может, еще появятся. Ведь автоматы уже, как утверждается, уже «на свалке истории», а задачи-то ни куда не делись? Надеюсь, корутины подхватят упавшее было знамя...;)
                      Но, про корутины. Вот, честно, — пытаюсь… Ломаю себя, меня корежит :0, но… как бы надо! Народ-то настоятельно требует вникнуть! ;)
                      Вроде бы нашел, что хотелось бы. Привожу цитату:
                      Корутины или сопрограммы – это возможность остановить выполнение функции в заранее определённом месте; передать куда-либо всё состояние остановленной функции вместе с локальными переменными; запустить функцию с того же места, где мы её остановили.

                      Все «ясно как божий день». Дальше — ступор. И не удивительно, что, похоже, не только у меня:
                      Очень многие до сих пор выступают против корутин. Зачастую жалуются на сложность их освоения, множество точек кастомизации и, возможно, неоптимальной производительности из-за, возможно, недооптимизированного выделения динамической памяти (возможно ;)).

                      Поясню почему так «корежит» меня. Просто потому, что базовую идею корутин (см. выше первую цитату) реализуют мои автоматы. В них «заранее определенным местом» останова являются состояния автомата. А ВКПа организует переключение между ними, сохраняя и восстанавливая их контекст. Ну, посудите сами, зачем мне корутины, если все на что они способны прекрасно реализуют автоматные сети. И подобное переключение и добавляя при этом много еще чего, о чем корутинам и не снилось.
                      Ну, а если я покажу код ядра ВКПа, то я, думаю, в ступор впадут уже «апологеты корутин». Но лично я «под капот» заглядываю только тогда, когда мне интересно. :) Я могу конечно ошибаться, но «корутинный двигатель» еще в старом наименовании «сопрограммы» показался мне бесперспективным (выше я уже сказал почему). И пока меня не переубедили в обратном. «Останавливать» я могу (легко!) и автоматами, а вот возможностей автоматов у корутин нет и в помине.
                      Хотелось-бы чтобы какой-нибудь «чат-бот» меня попробовал переубедить в обратном. Только аргументированно. Желательно на простых и понятных примерах типа «суммы массива», а не на примерах типа клиента GitHub. От подобных примеров мне «по ночам» снятся только кошмары… Уж больно… «долбежу много». Хочется чего-то попроще :)

                        0

                        Давайте я редуцирую ваш комментарий по моему правилу: нет кода отмены графа атвоматов -> пустой звук.


                        PS: https://lmgtfy.com/?q=dining+philosophers+with+coroutines

                          0
                          По поводу кода отмены графа автоматов…
                          Граф автомата представлен таблицей переходов (ТП) — массив строк LArc (см. исходный код примеров). Что значит «код отмена»? Автоматный процесс в системе представлен заголовком, который содержит указатель на ТП. Когда удаляется автоматный процесс, то удаляется заголовок и соответственно указатель на ТП. Вот и все «удаление графа».
                          По поводу «философов»…
                          Я, может, выразился не очень точно. Может, надо пояснить, что решение задачи и код этого решения — разные вещи. Это, как скажем, создание алгоритма нахождения корней уравнения и код реализации этого алгоритма. В моей статье о философах рассматривается поиск решения задачи. Это главное. Код с этим связан лишь косвенно. Например, упоминаемая там модель Хоара процессов фактически вообще отказывается решать эту задачу. Есть модель философов на сетях Петри. И здесь фактически нет решения, т.е. поиска модели и доказательства отсутствия/присутствия в модели тупиков. Модель сопрограмм беднее и модели Хоара и сетей Петри. Вот об этом речь. Потоки и корутины — это средства для реализации того или иного решения, но никак не математические модели.
                          В статье демонстрируется использование автоматов для реализации корутин. Если это все, на что способен «интеллект» компилятора в смысле генерации автоматов, то это весьма элементарные автоматы. Можно даже утверждать, что это совсем и не автоматы, а в лучшем случае некая имитация их. Но для реализации корутин даже таких возможностей, возможно, и достаточно. Но пока это лишь моя версия «автоматной трактовки» корутин ;)
                          И, кстати, подобные автоматы я уже, кажется, приводил ранее. И, да, даже их приходится рисовать ручками. Но это требование используемой автоматной технологии. А она должна быть единообразной и в простом и сложном случае. На то она и технология. И автомат в ее рамках фактически другой — классический, настоящий ;) И, более того, не просто автомат, а автоматная модель вычислений.
                            0
                            Давайте я редуцирую ваш комментарий по моему правилу: нет кода отмены графа атвоматов -> пустой звук.
                              0
                              Только для Вас ;)
                              Листинг. Переход автомата в следующее состояние
                              void TNetFsa::dispcNewSt (TASK &lst_tsk)
                              {
                              // установить новые текущие состояния
                                  cur_task= lst_tsk.flink;
                                  while (cur_task != &lst_tsk) {
                                      cur_wsp = cur_task->w_bl;
                                      if (!cur_task->bCancel&&!cur_task->pid->stop) {
                                          if ( (cur_wsp->arc_t&&cur_wsp->arc_b)||cur_wsp->bCall) {
                                              if (cur_wsp->bCall) {
                                                  cur_wsp->bCall = cur_wsp->bCall;
                                              }
                                              if (cur_task->w_bl->arc_t) {
                                                  cur_task->state = cur_task->w_bl->arc_t->szSt1;
                                                  cur_wsp->pFsaAppl->MooreAction();
                                              }
                                          }
                                      }
                                      // удалить "удаленные" задачи
                                      if (cur_task->bCancel) {
                                          while ( static_cast<void*>(cur_task->w_fl) != &cur_task->w_fl) {
                                              if (cur_wsp->pFsaAppl) {
                                                  cur_wsp->pFsaAppl->pWSP=nullptr;
                                              }
                                              cur_wsp->Clear();
                                              pStackWsp->Delete(cur_wsp);
                                              cur_wsp= cur_task->w_bl;
                                          }
                                          TASK* del_tsk = cur_task;
                              
                                          if (cur_task->pFsaAppl->pLFsaHdr) {
                                              LFsaHdr var;
                                              var.pLFsaAppl = cur_task->pFsaAppl;
                                              cur_task->pNetFsa->pCArrayNetFsa->pSetLFsaHdr->Clear(var);
                                          }
                              
                                          cur_task= cur_task->GetNextTask();
                                          del_tsk->Clear();
                                          pStackTsk->Delete(del_tsk);
                                      }
                                      else {
                                          string strRet;
                                          // удалить "завершенную" задачу
                                          if (!cur_wsp->arc_t) {
                                              strRet = cur_wsp->arc_b->szSt2;		// 
                                              if (strRet == "XX") {
                                              }
                                              else if (strRet == "00") {
                                                  string strARC;
                                              }
                              
                                              cur_wsp->Clear();
                                              cur_wsp->pFsaAppl->pWSP=nullptr;
                              
                                              if (cur_wsp->pFsaAppl->pCVarFSA) {
                                                  if (cur_wsp->pFsaAppl->pCVarFSA->pCurLFsaAppl) {
                                                      cur_wsp->pFsaAppl->pCVarFSA->pCurLFsaAppl = nullptr;
                                                  }
                                                  if (cur_wsp->pFsaAppl->pMainAutomaton) {
                                                      cur_wsp->pFsaAppl->pMainAutomaton->pNestedAutomaton = nullptr;
                                                  }
                                              }
                                              pStackWsp->Delete(cur_wsp);
                                              if (static_cast<void*>(cur_task->w_fl) == &cur_task->w_fl ) {
                                                  TASK* del_tsk = cur_task;
                                                  if (cur_task->pFsaAppl->pLFsaHdr) {
                                                      LFsaHdr var;
                                                      var.pLFsaAppl = cur_task->pFsaAppl;
                                                      cur_task->pNetFsa->pCArrayNetFsa->pSetLFsaHdr->Clear(var);
                                                  }
                                                  cur_task= cur_task->GetNextTask();
                                                  del_tsk->Clear();
                                                  pStackTsk->Delete(del_tsk);
                                              }
                                              else {
                                                  if (strRet=="XX") {
                                                      cur_task->w_bl->arc_b = nullptr;
                                                      cur_task->pFsaAppl->MooreAction();                    }
                                                  if (strRet=="00") {
                                                      cur_task->w_bl->arc_t = &cur_task->w_bl->arc_t->pNextState->ListArc.pRoot->FsaArc;
                                                  }
                                                  cur_task->w_bl->bCall=false;
                                              }
                                          }
                                          else {
                                              cur_task= cur_task->flink;
                                          }
                                      }
                                  }
                              }
                              


                              Там, где комменты типа // удалить... и выполняются последующие действия по удалению «автоматных структур».
                                0

                                Вы же понимаете, что вы не ответили на вопрос, а только ушли от ответа? Я вам уже кидал ссылку на постановку задачи: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/, но вы эту ссылку, как и всё, что не соответствует вашему убеждению "корутины — это автоматы, но беднее", проигнорировали. Я вам также говорил, что генерация автоматов — это только один способ реализации корутин, но вы игнорируете существование корутин, которые реализованы альтернативным способом, потому что это не покрывается вашей моделью "корутины — это автоматы, но беднее".


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


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


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


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


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


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


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


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

                                  0
                                  вы эту ссылку… проигнорировали.

                                  Зря Вы домысливаете… ;). Я, конечно, изучил материал, хотя и «по диагонали». Просто потому, что все эти проблемы с goto давно пройденный этап. Автоматному программированию на уровне модели управления не нужны не только они, но вообще любые другие операторы управления — while, if, switch и т.д. и т.п. Мне почему-то кажется, что для Вас это будет новостью ;). Да, наверное, и не только для Вас. Но в рамках действий они вполне применимы. И даже goto не запрещен. Только за рамки действия он не скакнет и это сводит на нет предполагаемый ущерб от его применения.
                                  И о модели. Исходя из определения корутин (см. приведенные цитаты) их применение это, как ни удивительно, вообще отказ от параллелизма. Декларируется чисто последовательное исполнение: — остановили одно — запустили другое, остановили то, вернулись назад и т.д. и т.п. Или я что-то напутал? ;)

                                  В реальном мире ошибки случаются.

                                  Вы, наверное, путаете реальный мир и мир реального корутинного программирования. В реальном мире, как это не обидно, даже нет математики. Математика, программирование и т.п. «реалии» — это результат творчества человека. Поэтому надо говорить об ошибках человека, а не реального мира. Но это уже философские материи. Перейдем к железу. Нет нужды проверять каждую операцию. На уровне железа это решено давно и достаточно элементарно — введением системы прерываний с реакцией на любые возникающие ошибки. Это все давно известные архитектурные «низкоуровневые решения». Модель фон Неймана (не ней мы сейчас работаем) обработку ошибок не учитывает. И, тем не менее, ее используют все и, как говорится, «не парятся» ;) Ваши исключения — это уровень языка высокого уровня, на ассемблере, насколько я припоминаю, этого фактически нет. Не использую их и я. И, как уже не раз говорил, все работает. Нужно только по максимуму не допускать возникновения таких ситуаций. Автоматные модели весьма компактны и позволяют провести полную их проверку и исключить по максимуму возникновение непредвиденных ситуаций. Конечно, «проколы» могут быть, но это уже проблема качества проектирования. Уровень логики модели — автомата, уровень предикатов и действий — методов объектов позволяют этот процесс провести качественно.
                                  Ну, Вы опять за свое. Это же элементарно ;) Машиной Тьюринга не пользуются в силу бедности ее операций (такая ограниченность — требование теории). Но я показал, как их можно просто расширить и, тем самым, ввести в работу и машину Тьюринга.

                                  не поддерживает другие...

                                  Наоборот. Буду утверждать, что любые парадигмы могут вполне сосуществовать вместе. Если об этом позаботиться, конечно. Есть многопоточность и потоки легко используются в АП (есть ситуации, когда это просто необходимо). Потоки в этом смысле очень удобны. На уровне потоков я могу без каких-либо помех и проблем использовать любой код. Еще раз — любой! Лишь бы был только возможен доступ к нему на уровне того же С++. И, кстати, появятся корутины в С++ и они будут доступны автоматам (может уже и есть — не знаю, не пробовал). А через корутины, или благодаря им, будет учтено и «существование других парадигм». Все продумано :)

                                  Корутины знают...

                                  Так и для автоматов это тоже не новость. Все что доступно на С++. А на этом уровне, думаю, доступно все. Или почти все ;)

                                  Автоматная же модель игнорирует...

                                  Не правда Ваша. Выше я сказал как — через те же потоки можно использовать любой код.

                                  Никто на это не пойдет.

                                  Опять не так. Опять в который раз Вы искажаете ситуацию. Уже не раз упоминался «автоматный MATLAB». У последнего, может, пользователь не меньше, чем у того же Kotlin-а. Но по любому — много, ну, очень много. И их число только множится.

                                  а не только одного из них...

                                  Речь не об одном автомате. Приведенный код удаляет все автоматы, которые так или иначе завершили свою работу (осознанно или по ошибке перешли в свое заключительное состояние).
                                    0

                                    Вы снова очень остроумно уходите от ответа.


                                    Ваши исключения — это уровень языка высокого уровня, на ассемблере, насколько я припоминаю, этого фактически нет. Не использую их и я.

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


                                    я могу без каких-либо помех и проблем использовать любой код. Еще раз — любой! Лишь бы был только возможен доступ к нему на уровне того же С++.

                                    Пусть теперь этот "доступный" код выкидывает исключение...


                                    Есть многопоточность и потоки легко используются в АП (есть ситуации, когда это просто необходимо). Потоки в этом смысле очень удобны.

                                    Код? Ибо вы переспросили меня "что значит перенос автомата на другой поток", из чего я сделал вывод, что автоматы и потоки не совместимы. Буду рад узнать, что я ошибся и перенос автомата на другой поток — допустимая операция.


                                    Зря Вы домысливаете… ;). Я, конечно, изучил материал, хотя и «по диагонали». Просто потому, что все эти проблемы с goto давно пройденный этап.

                                    Что подтверждает, что статью вы не читали. Она названа, на минутку, "Structured concurrency or: go statement considered harmful" в качестве омажа на известные статьи Дейкстры по структурному программированию. Продолжая его логику, автор статьи делает аналогичные выводы по поводу асинхронного программирования и необходимости структуры. Прочтите, всё-таки, статью, а после, жду кода отмены графа автоматов, то есть поддержки структурной конкурентности.


                                    И спасибо, что напомнили


                                    Автоматному программированию на уровне модели управления не нужны не только они, но вообще любые другие операторы управления — while, if, switch и т.д. и т.п.

                                    вы же понимаете, что это означает, что даже структурное программирование не поддерживается АП? И мы возвращаемся во времена до Дейкстры. И нет, это не новость для меня. Я уже говорил, что АП — пройденный этап и его нужно закопать...


                                    Уже не раз упоминался «автоматный MATLAB». У последнего, может, пользователь не меньше, чем у того же Kotlin-а.

                                    У матлаба или у его подмножества, поддерживающего АП?


                                    Кстати, раз уж вы завели разговор про популярность, сложите Python, C#, C++20, JavaScript, Kotlin, Go, Raku, Clojure, различные лиспы (скоро добавятся Java и Rust), чтобы оценить распространённость корутин. И это я перечислил только языки, про которые знаю, что они поддерживают корутины. Наверняка существуют еще сотни языков, которые корутины поддерживают корутины, о которых я даже не слышал.


                                    Отвечая на ваш вопрос:


                                    Декларируется чисто последовательное исполнение: — остановили одно — запустили другое, остановили то, вернулись назад и т.д. и т.п. Или я что-то напутал?

                                    Зависит от вашего определения последовательного исполнения. Инструкции корутины — да, нет возможности запустить паралельно инструкции одной корутины, их надо явно разделять на разный корутины.
                                    Сами корутины — почему бы и нет. Можно их запустить хоть параллельно, хоть последовательно, причем в любом порядке.
                                    Например, если у нас дан код


                                    coroutineScope {
                                      launch {
                                        println("1")
                                        println("11")
                                        println("111")
                                      }
                                      launch {
                                        println("2")
                                        println("22")
                                        println("222")
                                      }
                                    }
                                    println("3")

                                    то 11 будет распечана всегда после 1, 22 всегда после 2, и так далее. Но! Нет гарантий, что 2 будет всегда распечатан после 1. Последовательность запуска корутин и вообще одновременность их работы не гарантируется. Что гарантируется, так это то, что 3 будет распечатана и после 111, и после 222. Структурная конкурентность гарантирует, что основная корутина ждёт окончания работы обоих дочерних корутин.

                                      0
                                      Идея «структурного параллелизма», как представляется, явно походит на ярусно-параллельную форму. В которой мы переходим на следующий ярус, выполнив вычисления на текущем.
                                      Но настоящий параллелизм загоняется в какую-то «структурную форму». Он подобен «конвейеру», а не «пошаговому» исполнению ярусов.
                                      Подобным конвейером можно управлять, как на рис. 5 в статье. Но и в этом случае компоненты «параллельной ЯПФ» не прекращают свою работу даже при «переходах» с яруса на ярус, выполняя работу по синхроимпульсам.
                                      Конвейерная модель ближе к реальным условиям, когда параллельные объекты не «умирают», выполнив работу/вычисления, чтобы потом опять «родиться» (при приходе очередного синхроимпульса).
                                      Тем не менее, идея имеет право иметь быть. И тот же Ваш пример я представил бы в автоматной форме так:
                                      s0, s1, -, y1y2
                                      s1, s2, -, y3
                                      где
                                      y1() { println(«1»); println(«11»); println(«111»); }
                                      y2() { println(«2»); println(«22»); println(«222»); }
                                      y3() { println(«3»); }
                                      Здесь y1, y2, y3 — действия. Первый переход соответствует их параллельному исполнению. Второй переход идет за первым. Все так, как Вы описали. Форма другая. Возможно не столь привычная и «элегантная», но (на мой, безусловно, субъективный как бы взгляд) она математически и технологически более выверенная и понятная (если к ней привыкнуть и понять ее прелести ;)). Здесь при все желании исключены какие-то иные трактовки исполнения действий.
                                      Таким образом одним автоматом, используя его свойства, мы описали (и реализовали) параллелизм Вашего примера. Но это, как я сказал параллелизм достаточно ограниченный, связанный с управлением действиями.
                                      Настоящий параллелизм — это множество подобных автоматов. Замечу, автоматов функционирующих постоянно и имеющих устойчивые связи друг с другом. Это как «философы», «стрелки» и т.п. Схема может быть динамической подобно жукам, но их обсуждение оставим на будущее.
                                      На связи объектов при этом не накладывается каких-либо ограничений. Но, что интересно, даже при такой казалось бы анархии и беспорядочности связей (в отличие от упорядоченности ЯПФ), эти объекты приводятся к одному объекту. И это уже то, с чего мы начали разговор — со структурного параллелизма. Проблема в одном — как доказать эквивалентность схемы одному автомату (т.е. соответствии схемы структурному параллелизму). К счастью, это возможно.
                                      Итак. Идея «структурного параллелизма» в чистом виде, похоже, описывается одним автоматом. И в этом есть ее ограничение.
                                      Возможно (и, скорее, всего), она проще с точки зрения обработки ошибок/исключений. Но это не главная здесь проблема, т.к. «действия» оттестировать, чтобы они не выбрасывали исключений, думаю, не такая уж большая проблема (в конце концов, если существует опасность того же деления на ноль, то — проверяйте).
                                      И еще. Отсутствие гарантий — плохо. Очень плохо ;) Недетерминизм потоков — основная их проблема. И с корутинами получается опять та же «байда»? Ну, надо же делать выводы из ранее допущенных ошибок!?
                                      И также по поводу «отмены графов автоматов». Другого кода просто нет. Кстати, действие (одно!) на переходе может создавать «граф» (вложенный автомат). И он удаляется при завершении действия. Даже если это действие порождает рекурсию автоматов (графов).
                                      Ошибки, как Вы уже понимаете ;), исключаются на этапе проектирования автоматов.

                                      Да, о потоках. Реализация на примере простого циклического счетчика типа:

                                      int n = 100000000;
                                      while (n>0) {
                                      n--;
                                      }

                                      Вот код (только не надо пугаться :)):

                                      Создание автоматом потока
                                      #include «lfsaappl.h»
                                      #include class FCount;
                                      class ThCount: public QThread
                                      {
                                      Q_OBJECT
                                      public:
                                      ThCount(FCount *p, QObject *parent);
                                      protected:
                                      bool bIfRun{false};
                                      bool bIfStop{false};
                                      bool bIfExecuteStep{false};
                                      FCount *pFCount;
                                      void run();
                                      friend class FCount;
                                      };

                                      class FCount:
                                      public LFsaAppl
                                      {
                                      public:
                                      void ExecuteThreadStep();
                                      void WaitForThreadToFinish();
                                      LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FCount(nameFsa); }
                                      bool FCreationOfLinksForVariables();
                                      FCount(string strNam);
                                      virtual ~FCount(void);
                                      ThCount *pThCount{nullptr};
                                      CVar *pVarCount;
                                      CVar *pVarExtVarCount;
                                      CVar *pVarStrNameExtVarCount;

                                      protected:
                                      int x1(); int x12();
                                      void y1(); void y2(); void y12();
                                      friend class ThCount;
                                      };

                                      #include «stdafx.h»
                                      #include «FCount.h»
                                      #include static LArc TBL_Count[] = {
                                      LArc(«st», «st»,"^x12",«y12»), //
                                      LArc(«st», «s1»,«x12», «y2»), //
                                      LArc(«s1», «s2»,«x1», «y1»), //
                                      LArc(«s2», «s2»,"^x1", «y2»), //
                                      LArc()
                                      };

                                      FCount::FCount(string strNam):
                                      LFsaAppl(TBL_Count, strNam, nullptr, nullptr)
                                      { }

                                      FCount::~FCount(void) { }

                                      bool FCount::FCreationOfLinksForVariables() {
                                      pVarCount = CreateLocVar(«n», CLocVar::vtInteger, «local counter»);
                                      pVarExtVarCount = nullptr;
                                      pVarStrNameExtVarCount = CreateLocVar(«strNameExtVarCount», CLocVar::vtString, «external counter name»);;
                                      string str = pVarStrNameExtVarCount->strGetDataSrc();
                                      if (str != "") {
                                      pVarExtVarCount = pTAppCore->GetAddressVar(pVarStrNameExtVarCount->strGetDataSrc().c_str(), this);
                                      }
                                      return true;
                                      }

                                      void FCount::ExecuteThreadStep() {
                                      if (pThCount) pThCount->bIfExecuteStep = true;
                                      }

                                      void FCount::WaitForThreadToFinish() {
                                      if (pThCount) {
                                      // завершить поток
                                      pThCount->bIfRun = false;
                                      pThCount->quit();
                                      pThCount->wait(500);
                                      pThCount->terminate();
                                      }
                                      delete pThCount;
                                      }

                                      int FCount::x1() { return pVarCount->GetDataSrc() > 0; }

                                      int FCount::x12() { return pVarExtVarCount != nullptr; }
                                      // создаем поток
                                      void FCount::y1() {
                                      pThCount = new ThCount(this, pTAppCore->pMainFrame);
                                      }

                                      void FCount::y2() {
                                      pVarCount->SetDataSrc(this, pVarExtVarCount->GetDataSrc());
                                      }

                                      void FCount::y12() { FInit(); }
                                      // поток поток поток поток поток поток поток поток поток поток
                                      ThCount::ThCount(FCount *p, QObject *parent):
                                      QThread(parent)
                                      {
                                      pFCount = p;
                                      bIfRun = true; // установить признак запуска/завершения потока
                                      start(); // запустить поток
                                      }
                                      // цикл исполнения потока
                                      void ThCount::run()
                                      {
                                      bIfStop = false; // сбросить признак останова потока
                                      // исполнять пока установлен признак работы потока
                                      // так не возникает ошибки при закрытии приложения
                                      // (установлено опытным путем)
                                      int n = int(pFCount->pVarCount->GetDataSrc());
                                      while(bIfRun) {
                                      if (bIfExecuteStep || pFCount->pVarCount->GetDataSrc() > 0) {
                                      bIfExecuteStep = false;
                                      n = int(pFCount->pVarCount->GetDataSrc());
                                      while (n>0) {
                                      n = int(pFCount->pVarCount->GetDataSrc());
                                      pFCount->pVarCount->SetDataSrc(pFCount, --n);
                                      pFCount->pVarCount->UpdateVariable();
                                      }
                                      n = int(pFCount->pVarCount->GetDataSrc());
                                      }
                                      }
                                      bIfStop = true; // установить признак останова потока
                                      }

                                        0

                                        За код потока спасибо!


                                        Однако, я имел в виду наоборот: есть два потока, А и В. автомат сначала работает на потоке А, потом работает на потоке В, потом возвращается на А. Зачем это нужно? Это нужно для вывода чего-нибудь в главное окно программы. В данном случае В — это главный поток и только в нем разрешена отрисовка. На корутинах это делается элементарно:


                                        // Здесь мы в потоке A
                                        withContext(UI) {
                                          // А здесь уже в потоке В
                                          window.draw(circle)
                                        }
                                        // И снова в А

                                        Конвейерная модель ближе к реальным условиям, когда параллельные объекты не «умирают», выполнив работу/вычисления, чтобы потом опять «родиться»

                                        Именно поэтому я говорю, что термин "сопрограмма" неудачен, более удачен был бы "сопроцедура": как и обычные процедуры, сопроцедуры запускаются, работают и завершаются. Могут запуститься ещё раз, если например, их вызывают в цикле.


                                        Первый переход соответствует их параллельному исполнению. Второй переход идет за первым.

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


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

                                        То есть, единственное отличие от потоков в наличии синхросигналов. Проще, всё-таки, в кремнии выпекать, чем пытаться синхронизоваться при наличии операций с неопределенным временем исполнения (загрузка файла по сети, как пример).


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


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

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


                                        как доказать эквивалентность схемы одному автомату (т.е. соответствии схемы структурному параллелизму).

                                        Это, к сожалению, не возможно, из-за того, что структурная конкурентность описывает поведение


                                        1. В конкурентной среде, с недетерминированным временем запуска и завершения
                                        2. При возникновении ошибки.

                                        Это абсолютно другая модель, которая не покрывается АП, но соответствует тому бардаку, что творится в современных ОС.

                                          0
                                          На корутинах это делается элементарно

                                          В аналогичных ситуациях удобнее разделять работы. Например, за конкретные действия отвечает один автомат, за отображение и ввод данных — другой. Первый работает с максимальной скоростью, второй — гораздо медленнее (слишком частое отображение все равно не видно). Обычно это разные автоматные пространства. Тот же автомат отображения можно запускать/создавать только тогда, когда это необходимо, чтобы что-то посмотреть. Таким образом каких-то «прыжков» нет.
                                          Даже приведенный код потока нужен только для выполнения определенной работы. Или очень быстрой или, наоборот, очень медленной, тормозящей основной поток.
                                          Корутины умирают и передают ошибку наверх, совсем как процедуры.
                                          Лучше рассчитывать на «жизнь». Безусловно, может случиться что угодно, но… для этого и есть процедура отладки. Ошибки действий фактически не рассматриваются. Появятся — будет, не скрою, скорее всего крах :( Значит, не все отловили на этапе отладки…
                                          Ибо пока один поток работает ..., другой поток может...
                                          Собственно так работают и автоматы разных автоматных пространств. Но, особенно для «чужого кода», можно использовать и потоки (см. мой пример). А недетерминизм, за исключением слабо взаимодействующих процессов, — это очень плохо. Та же теория асинхронных автоматов именно из-за этого так и не сложилась :(
                                          операция загрузки выкинула ошибку. Как такое можно предотвратить до начала загрузки?
                                          Это надо предусматривать при проектировании того же автомата. Например, работая с сетью, я контролирую время ответа. Если превышено — выполняю те или иные действия.
                                          Это абсолютно другая модель, которая не покрывается АП, но соответствует тому бардаку, что творится в современных ОС.

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

              0
              Извините, а зачем нужны эти ограничения:
              нельзя, чтобы совместное поведение процессов было непредсказуемым.
              непрерывность ее функционирования, когда любое изменение входных данных приводит к пересчету результата
              Насколько я понимаю, достаточны требования к программисту, чтобы входные данные были неизменяемыми, каждая сопрограмма была локальна по данным (SIMD/MIMD), а перед выходом стоял примитив синхронизации. Зачем нам знать в каком порядке выполняются потоки, если они независимы по входным данным?
                0
                Первое «ограничение» проистекает из свойства однозначности алгоритма: 2x2 всегда должно быть 4, а не, как в анекдоте, примерно «сэм-восэм»…
                Второе — не ограничение. В последовательном программировании 2x2 — оператор, в параллельном это, как в статье, может быть процесс, реализующий операцию умножения над входными данными.
                Требование неизменяемости входных данных — нереальное требование. Например, что такое «неизменяемая синусоида» на входе?
                В чем смысл синхронизации «перед выходом»? Зачем и с кем синхронизироваться?
                Безусловно, результат не должен зависеть от порядка работы потоков. Именно этой теме и посвящен пример в статье. Если он зависит от порядка — это уже не параллельные потоки.
                Параллельные потоки, которые не связаны между собой через входные данные, фактически не интересуют теорию параллельных вычислений. Их можно изучать по отдельности в рамках обычной теории последовательных вычислений.
                И еще. Параллельное программирование — это переход к проектированию процессов. Последовательное программирование — это реализация операторов, функций, программ. Т.е. это качественно разные уровни проектирования. Обычная программа обязательно заканчивается, параллельная — может работать очень долго и даже бесконечно долго ;)
                  0
                  Требование неизменяемости входных данных — нереальное требование. Например, что такое «неизменяемая синусоида» на входе?
                  В зависимости от того, что обрабатываем: или конкретный семпл, или весь временной ряд (массив, список) на момент входа в обработчик. Т.е. на обработку уходит копия, а буфер продолжает копить семплы.
                  В чем смысл синхронизации «перед выходом»? Зачем и с кем синхронизироваться?
                  Допустим, на определенной стадии мы выполняем суммирование массива и константы, запускаем потоки по числу элементов массива, каждому из которых передаём элемент массива и константу. Чтобы передать результат суммирования дальше, надо дождаться окончания работы всех потоков.

                  Имхо, параллельность не означает реактивность.
                    0
                    Т.е. на обработку уходит копия, а буфер продолжает копить...
                    Для определения вычислительной модели (машины) достаточно памяти в форме ленты, т.е. обычной памяти. Далее, если нужно, уже на ее базе организуется любая структура — стек, буфер и т.п. С точки же зрения на динамику работы модели в пределах одного дискретного такта входные данные по определению считаются неизменяемыми. В реальности мы стремимся к минимизации дискретного такта (повышению тактовой частоты процессора).
                    Чтобы передать результат суммирования дальше, надо дождаться окончания работы всех потоков.
                    Спасибо за хороший пример для демонстрации возможностей автоматной модели в сравнении с теми же потоками:). Автоматам для решения этой задачи не нужна синхронизация, потокам даже в таком простом случае (!) — нужна.
                    Имхо, параллельность не означает реактивность.
                    На мой взгляд так она вообще не имеет отношения к параллелизму.
                      0
                      Где-то на уровне ядра или железа мы всё равно упрёмся в пул потоков и планировщик, зачем упрощать абстракцию в пользу академичности, если для работы придётся держать в голове другую?
                      в пределах одного дискретного такта входные данные по определению считаются неизменяемыми
                      А ещё они могут быть недоступны, заняты, в процессе изменения и пр. Спокойнее копирование вызвать :)
                      Автоматам для решения этой задачи не нужна синхронизация, потокам даже в таком простом случае (!) — нужна
                      Допустим, следующая операция — усреднение или ещё что, обращающееся к нескольким элементам массива. Как тут без синхронизации, если один из потоков для локальной окрестности не успел завершиться?
                      На мой взгляд так она вообще не имеет отношения к параллелизму.
                      Код должно быть проектировать, писать и сопровождать удобно. Зачем у автомата собственная память? Лично меня это сбивает, я бы в явном виде воткнул буфер.
                        0
                        … придётся держать в голове другую?
                        Мне сложно угадать, что у Вас в голове :) В данном случае я бы сказать — выкиньте из своей голове мысли о потоках и т.п. ;) Они Вам не понадобятся (ни потоки, ни мысли о них).
                        Спокойнее копирование вызвать
                        В пределах дискретного такта, Вы не поверите, — делайте с данными все что угодно: анализируйте занятость, копируйте и т.д.
                        Как тут без синхронизации
                        . Сложно уловить ход Ваших мыслей… Это уже другой пример. Здесь — никак. В подобных ситуациях автоматы дают механизм синхронизации — через состояния процессов. Которого, кстати, нет у потоков.
                        Зачем у автомата собственная память? Лично меня это сбивает,
                        Чтобы не сбивало… У автоматов нет «памяти». У них есть состояния. Это разные вещи. Состояния тем и примечательны, что их не нужно куда-то «втыкивать». Они — свойство автоматной модели ;)
                          0
                          В данном случае я бы сказать — выкиньте из своей голове мысли о потоках и т.п. ;)
                          Мой мозг измучен микроконтроллерами, если я последую вашему совету, то потеряю в профпригодности :)
                          В пределах дискретного такта, Вы не поверите, — делайте с данными все что угодно: анализируйте занятость, копируйте и т.д.
                          Если считать память одноуровневой, а процессор однозадачным и без прерываний — всё отлично. А если ближе к жизни, то память может быть занята, может быть промах кэша, из DMA или прерывания могут изменить читаемый кусок памяти… В общем, появляются состояния «ждать» и «лопатить неверные данные».
                          автоматы дают механизм синхронизации — через состояния процессов. Которого, кстати, нет у потоков.
                          Семафоры и мьютексы появились несколько позднее концепции потоков, но состояния у потока есть, пусть оно и «внешнее».
                          У автоматов нет «памяти».
                          Вот в этой статье вы писали про теневую память автомата, по описанию это буфер внутри автомата.
                            0
                            За микроконтроллеры — «уважуха» :) Когда-то тоже приложил к этому руку. Так что по поводу потоков смею утверждать — вполне можно без них.
                            … но состояния у потока есть
                            Нужно быть точным — нет. «Внешнее» — не считается. Так можно считать, что и автоматы есть. Только «внешние» ;)
                            Вот в этой статье вы писали про теневую память автомата
                            Вы немного не поняли. Я рассматриваю модель автомат+память+операции (об этом точнее в статье). «Плюс», а не «внутри». Например, есть такое понятие — магазинные автоматы. Вот там скорее «внутри». Хотя тоже — с определенной натяжкой.
                              0
                              Так что по поводу потоков смею утверждать — вполне можно без них.
                              Я не буду плакать про планировщик RTOS или NVIC, но объясните — как, если инструкции минимум двухтактные, и между первой и второй может прийти прерывание и испортить память? Тут с «сырыми» volatile данными очень неуютно работать.

                              Ещё такой вопрос: а можно весь автоматный формализм через трансляцию перенести в движок или компилятор? Мне комфортнее писать a[i]+b и не думать, что выдаст компилятор: loop unrolling, векторные инструкции или перенос на видеокарту.
                                0
                                но объясните — как, если инструкции минимум двухтактные, и между первой и второй может прийти прерывание и испортить память
                                Я настолько не доверяю потокам и планировщикам, что лучше написал бы свой «планировщик». Собственно и в Windows и Linux он есть, как есть и прерывания. Было это давно, но насколько я припоминаю в подобных ситуациях (если это необходимо, конечно) существует маскирование прерываний. Если это необходимо делать тупо постоянно, то все это (маскирование) можно встроить в свой планировщик, отменяя прерывания на время выполнения действий.
                                Я лично пользовался своим планировщиком. Был он простой «как топор» (но и зато надежный, как он же) и при необходимости я его дополнял.
                                Потом, как представляется, это не очень типовая ситуация. В большинстве случаев прерывания должны работать прозрачно и не «портить» что-то. Но, еще раз, если такая опасность есть, то на время запрещаем их работу. Как-то так.
                                а можно весь автоматный формализм через трансляцию перенести в движок или компилятор
                                Что-то можно. Например, в ВКПа таблица переходов автомата преобразуется как-то похоже в промежуточную (внутреннюю) форму. Это удобно. Но саму таблицу переходов (граф автомата и т.п.), т.е. собственно автомат, нужно создавать самому. Ведь, алгоритмы компьютер (язык и т.п.) не создает за Вас? Так ведь? Или Вы поручаете программы писать искусственному интеллекту (о чем многие все еще мечтают).
                                Что-то можно, конечно, сделать. Те же нейронные сети, генетические алгоритмы и т.п. вещи генерятся. Но, во-первых, это узкий/специализированный класс задач и работу «головой» ни как не отменяет.
                                Так что хочу огорчить или, наоборот, обрадовать — труд программиста еще долго ни какие ИИ не заменят. Не ленитесь — пишите алгоритмы. Автоматные, конечно. И испытываете от этой работы такое же удовольствие, как и я… Только так… На обычное программирование я сейчас смотрю как на какой-то давний кошмарный сон. Все автоматами, все — параллельно...;)
                                Не буду обнадеживать ;) На микроконтроллерах реализация того, что есть в ВКПа и в рамках обычной ОС, будет скорее проблематична. Но, например, свою работу я строил так. На обычном компе я отрабатывал алгоритмы, а потом в рамках достаточно простой процедуры все переносилось на микроконтроллер. Были, конечно, проблемы на уровне тех самых прерываний. Но это все было достаточно типово и, раз решенное, оно работало потом всегда.
                                  0
                                  Да, запрещение прерываний на входе в обработчик типовое решение, кроме тех редких случаев, когда необходимы вложенные прерывания, например, для обработки нерегулярных, но очень важных событий. Но ещё есть «главный цикл», где часто выпоняется несрочная, но времяёмкая работа, и на входе в который прерывания запрещать неправильно. Так что я предпочитаю налаживать общения между прерываниями через буферы, по максимуму разобщая по изменяемым данным.
                                  Я не спорю, что автоматы удобный инструмент и формализм, но рафинированном, а тем более без внешнего планировщика времени выполнения, виде они как-то не очень ложатся на железо.
                                  Ведь, алгоритмы компьютер (язык и т.п.) не создает за Вас?
                                  Ну иногда создаёт, те же Pinhole оптимизации контролера, когда, например, суммирование или умножение заменяется сдвигом. Если можно разделить AST на независимые по данным ветви, то при AOT-компиляции ветви будут запущены независимо (последовательно или параллельно), чтобы на момент схождения независимые вычисления были завершены. Другие оптимизации могут разделить или изменить цикл, вынеся инвариант наружу, или выкинуть мёртый код, к которому нельзя перейти из основного тела программы. В общем, если дизассемблировать код, компилированный с -о3, то можно легко не узнать свою программу. Другой пример — средства языка, например, реализация «ленивых» вычислений на С заставляет частично переписать компилятор Лиспа для вставки в свою программу.
                                    0
                                    Так что я предпочитаю налаживать общения между прерываниями через буферы, по максимуму разобщая по изменяемым данным.
                                    В подобной ситуации это, думаю, уже не предпочтения, а необходимость ;)
                                    они как-то не очень ложатся на железо
                                    Предпочитаю работать именно с железом и автоматы здесь, исходя из моего опыта, единственная адекватная модель. Кстати, ее и разработали-то «железячники». Уж они-то не стали бы себе делать «козу» ;)
                                    Ну иногда создаёт
                                    Иногда — это специфические ситуации. Но даже их, если подумать, можно реализовать часто более эффективно. В общем же случае все равно нужно думать самому, создавая алгоритм, как обычный, так и автоматный. Да и зачем лишать себя удовольствия — думать ;) Реализацию модели, да, можно поручить компилятору или кому-то или чему-то другому. Например, тот же Stateflow в MATLAB. Вы рисуете автоматы и он создает их код. В том числе и для микроконтроллеров. Все прекрасно. Для меня в этом только одна проблема — в той модели автомата, которой соответствует пакет. В данном случае она несколько не соответствует оригиналу.
                                    По поводу «ленивости». Не скрою, что по мне это просто «изврат». И вообще функциональное программирование — «формально правильно, но издевательски по существу» ;)
                                      0
                                      В подобной ситуации это, думаю, уже не предпочтения, а необходимость ;)
                                      Я думаю, что других ситуаций с имеющимися архитектурами не бывает :)
                                      Предпочитаю работать именно с железом и автоматы здесь, исходя из моего опыта, единственная адекватная модель. Кстати, ее и разработали-то «железячники».
                                      А чья теория и реализация? Насколько я знаю, первые «два в одном» были у Глушкова, но помню, что там был общий синхроимпульс. Тут работать всё будет надёжно, но весь «конвеер» встаёт целиком, если на какой-то из стадий затык.
                                      И вообще функциональное программирование — «формально правильно, но издевательски по существу» ;)
                                      Было много статей на хабре про то, как ФП правильно готовить. Конечно, если неправильно приготовить те же ленивые вычисления, то очень просто наесться памяти и уснуть, как и при неправильном динамическом программировании.
                                        0
                                        других ситуаций… не бывает :)
                                        ну, согласен :) Особенно, если речь о прерываниях.
                                        … был общий синхроимпульс
                                        Был и насколько я понимаю остался. Другие попытки типа асинхронной схемотехники (Варшавский и т.п.), похоже, себя не оправдали. И потом у Глушкова (а также Мелихов, Баранов и др...)больше про синтез автоматов, а не про «конвейеры». С гонками проще всего справляться синхронизацией. Без — одни проблемы, теоретическое решение которых, насколько я в курсе, не найдено. Может, правда, что-то и упустил ;)
                                        По поводу ФП. Базовая его идея — начинать с одной функции теории не противоречит (все можно интерпретировать как один большой «черный ящик»), но для практики, где мир многокомпонентен (много взаимодействующих «ящиков»), не подходит. А неправильно реализовать можно что угодно. Вот и многопоточность — совсем не правильная…
                                          0
                                          Пока есть синхроимпульс, не совсем верно говорить про «активный КА», я правильно понимаю, что синхроимпульс может быть переменной частоты, генерируемым по мере готовности предшественика? А с асинхронными и самосинхронными автоматами всё ещё грустно.

                                          Если не упарываться по максимально чистому ФП типа языка Lambda, то идеи вполне здравы и могут быть применены к любой парадигме:
                                          — Чистые функции: функция принимает только неизменяемые аргументы, переданные ей в явном виде, и имеет такой же явный выход
                                          — Нет глобальному состоянию и глобальным переменным
                                          — Разделение IO (где может быть внезапное изменение данных, независимое от программиста) и остального потока программы (где есть какие-то гарантии и зависимость от программиста_
                                          Ну и прочие плюшки типа композиции функций и частичного выполнения.
                                            0
                                            Автоматы всегда «активны». Других в теории попросту нет. Синхроимпульсы отсчитывают такты. У синхронных автоматов длительность такта фиксированная, а асинхронных плавающая. Соответственно синхроимпульс выдает или генератор или специальная схема. Классификация по Глушкову. У других определение асинхронных автоматов другое. Мне ближе и понятнее определение по Глушкову. Если Глушкова читали (или того же Баранова по микропрограммным автоматам), то это должно быть Вам известно и понятно.
                                            И, безусловно, автомат работает с теми данными, которые «схватил» на начало такта, а все что в пределах такта им пропускается. А потому длительностью такта и определяется его полоса пропускания, как прибора. Все эти понятия применимы и к автоматным программам. Но к тем, которые на любом функциональном языке, это будет сделать, как мне кажется, сложно :) Хотя бы потому, что в этой модели отсутствует понятие модели времени.

                                            Ну а как без функций и рекурсии на их основе? Никак. Вот и в ВКПа есть вложенные автоматы и на их основе функции и даже рекурсия. Думаю и любые «плюшки» на этой базе можно реализовать. Если, конечно, надо и имеет смысл.
                                              0
                                              Надо бы Глушкова перечитать, там много математики, поэтому и осилил плохо.
                                              Как работает композиция КА, если предыдущий генерирует данные быстрее, чем последующий потребляет?
                                                0
                                                Мне так больше Баранов нравился — ближе к практике. По теории автоматов (особенно композиции) — Мелихов. Хотя начинал с Майорова. Много про автоматы почерпнул из теории языков. Но все надо фильтровать, т.к. все эти книги про железо. В программировании многие их проблемы просто по барабану. Особенно их специфический синтез. Поэтому отброшу скромность — читайте мои последние статьи. Там выжимка того, что надо. И по определению автомата. Это и вложение и их параллельная композиция. Для программиста важна машина. Про это Вы совсем не прочитаете.Про инерционность не найдете. Пр композицию тоже нюансы. Тот же Мелихов дает прекрасную теорию, но для абстрактных автоматов, а программисту удобнее — структурные. Про «машину» — только у меня. Про инерционность — только у меня. Про «теневую память» — только у меня. Про реализацию сетевой модели — только у меня. Я, я, я — аж неудобно стало :) Но, как в анекдоте, «куда умище-то денешь» :)
                                                Но еще раз. Все мои последние статьи на Хабре — это последовательная попытка изложить того, к чему я пришел. И фактически я все уже и изложил. Дальше пойдут уже детали…
                                                Как работает композиция КА, если предыдущий генерирует данные быстрее, чем последующий потребляет?

                                                Как и положено — будет пропускать данные. Полная аналогия с железом. Есть полоса пропускания (по Котельникову) и это ни кто не отменял. Если хотите, чтобы работало без пропусков. То 1) приведите автоматы к одной скорости 2) объедините их в сеть с единым дискретным временем. И тогда все проблемы взаимной их работы уйдут. Вы просто о них забудете. Это так будет в ВКП(а). «Железячник» введет в схему генератор и засинхронизирует схемы автоматов. Программисту (в ВКПа) этого не надо. Поскольку за это отвечает ядро, в которое вы грузите автоматы, помещая их в общее автоматное пространство. Замечу, что смоделировать Ваш пример можно, поместив автоматы в разные автоматные пространства. Я так поступаю с диалогами. Они — медленный автомат.
                                                  0
                                                  Почитаю Баранова и повторно воскурю статьи, спасибо, но хотелось бы чего-то базового, не такого академического, «тех же щей, но пожиже влей». Проблема не в том, что я не понимаю ваши статьи или код в них, проблема в том, что я не понимаю — зачем и почему именно так? Разбить код на блоки, каждый имеющий закрытые входной и выходной словари и ограниченное количество (лучше всего одну) меняющихся переменных внутреннего состояния бывает удобно, но часто кажется недостаточно удобным инструментом, чтобы вывихивать мозги и писать так всегда. Сам я больше всего почерпнул про КА из «Design Patterns for Embedded Systems in C» и мануалов по TLA+, но там написано более «бытово».
                                                  Как и положено — будет пропускать данные.
                                                  Понятно, что нужно или уравновесить количество консументов и продуцентов, или ввести планировщик. Вопрос в том, что будет без планировщика, если один из КА кинет исключение или иным способом нарушит тайминг. Если не делать ничего, то в «железе» это быдет значить, что на одном из блоков уползла фаза генератора.
                0
                проблема в том, что я не понимаю — зачем и почему именно так?
                Чем для меня прежде всего интересен Баранов? Он понятно и доступно показывает как от ГСА (граф-схемы алгоритма) перейти к автомату. Должно быть понятно, что современный программист мыслит по типу ГСА. Пусть даже не рисует блок-схемы. Языки программирования именно такие. Они формируют подобное мышление. И Баранов, похоже, это понял. Понял, что так проще в существующей ситуации, когда сложно программиста убедить мыслить автоматами, когда его научили мыслить блок-схемами.
                А зачем тогда автоматы? У автоматов в отличие от ГСА есть теория. Для автоматов есть теория и практика синтеза схем, которых нет для ГСА/блок-схем. Т.е. Баранов — это компромисс между существующим мышление и необходимостью перехода от него к мышлению, которое диктуется теорией и практикой проектирования цифровых схем. Как только мы (точнее Баранов) предложил процедуру перехода от ГСА к КА дальше пошло по накатанному тем же Глушковым пути.
                Вопрос. Зачем ГСА, если можно было бы сразу создавать автомат? Инерция мышления проектировщика, которого не учили так мыслить. Вывод, если Вы научитесь и/или не хотите мыслить автоматами, то для Вас они не понятны и не интересны будут по определению.
                Перейти на другое мышление сложно. Очень сложно. Чтобы это произошло нужно понимание зачем нужны Вам автоматы, в чем их преимущество. Баранову понятно зачем — чтобы провести синтез. А программисту — на фига все эта круговерть? И на это есть ответ. Автомат — модель, которая качественно совершеннее модели блок-схем. У не есть теория и там все на эту тему расписано. Хотя, конечно, есть в ней и пробелы. Но пока не о них речь.
                Итак. Предположим Вам стало ясно, что автоматы Вам необходимы (я опускаю обсуждение почему). Без них Вам нет жизни ;) Как теперь их реализовать. Баранов предлагает теорию их реализации (синтеза). А Вам-то что от этого? Да почти ничего. Железо и программа — разные объекты. Как поступает программист — реализует в рамках текущего свое мышления, т.е. используя конструкцию типа switch. На эту тему есть целая технология — «свист-технология» имени Шалыто А.А. Собственно этот же подход и в упомянутой Вами книге. Я просмотрел ее «по диагонали». Все по списку — UML, модель Harel-а, switch-реализация и т.д. и т.п. Вроде автоматы, но и не автоматы. Я, думаю, что любой англичанин отличит любого русскоязычного, говорящего на английском языке. Даже прекрасном. Это тонкости, которые, порой, очень сложно объяснить.
                С автоматами, к счастью, — можно. ;) Нужно только мыслить настоящими автоматами, а не автоматами Харела, автоматами UML и держать в голове их «свист-реализацию». А чтобы так мыслить нужна автоматная машина, имеющая автоматный язык. Среда ВКП(а) и ее «автоматный С++» именно это и реализует. Программист рисует автомат в форме таблицы переходов и дальше его процесс реализации не интересует. Он мыслит чистыми автоматами. Не корявыми автоматами Харела, а более простой, но не менее мощной, классической моделью автомата. Именно для этих автоматов есть теория, а не для автоматов Харела или тех же автоматов Шалыто. И именно подобные классические автоматы с необходимыми для современного программиста дополнениями/расширения — «плюшками» типа вложения автоматов и реализации параллелизма описаны в моих статьях.
                Но вот как-то так. В своих статьях я фактически не описываю автоматы. Подробно это делаю упомянутые нами авторы. Я описываю то, что из них (их подмножество) нужно, чтобы они были удобны и понятны программисту. А на этом можно уже строить и другие автоматы — абстрактные, структурные (в обычном понимании) и т.д. и т.п.
                «Почему люди не летают?...» я бы перефразировал «Почему программисты не мыслят автоматами?...» :) Баранов убедительно показал и доказал как от ГСА-мышления можно перейти КА-мышлению. И тем самым ответил на Ваш вопрос — «зачем и почему именно так?»

                Я только несколько подправил язык классических автоматов, которые использует Баранов, Глушков и др. Так, я добавил вложения, инерцию, и параллелизм (см. мою модель управления автоматных программ). Ну и ввел их в «автоматную машину»… ;) Да, а за их «синтез» отвечает ядро ВКП(а).
                Уф… Почти статью накатал :)
                Да, благодаря Вам я теперь знаю что такое даже редуценты ;)
                  0
                  Был давно асинхронный процессор, без тактовый. Для таких вещей отлично подошел бы.
                  А существуют ли какая-либо аппаратная оптимизация рекурсий?

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое