Zum Wettbewerbskorutinismus (am Beispiel der reaktiven Programmierung)

1. Einleitung


Der Wettbewerb um die Köpfe, Stimmungen und Bestrebungen der Programmierer ist meines Erachtens ein moderner Trend in der Entwicklung der Programmierung. Wenn fast nichts vorgeschlagen wird, obwohl unter dem Motto des Kampfes dafĂŒr. Es ist sehr, sehr schwierig, im GedrĂ€nge von Software-Paradigmen etwas Neues zu erkennen, das sich in der Tat oft als recht bekannt und manchmal einfach veraltet herausstellt. Alles wird durch terminologische Freuden, ausfĂŒhrliche Analysen und mehrzeilige Beispiele in vielen Programmiersprachen „weggespĂŒlt“. Gleichzeitig werden Anfragen, den Hintergrund der Lösung zu öffnen und / oder zu berĂŒcksichtigen, die Essenz von Innovationen hartnĂ€ckig vermieden, die Versuche herauszufinden, wie viel dies benötigt wird und was am Ende geben wird, was die Innovation qualitativ von bereits bekannten AnsĂ€tzen und Programmierwerkzeugen unterscheidet, im Keim vereitelt.

Ich erschien auf HabrĂ©, wie in einer der Diskussionen treffend bemerkt wurde, nach einem gewissen Einfrieren. Es wird mir nicht einmal etwas ausmachen. Zumindest ist der Eindruck anscheinend genau das. Daher stimme ich zu, ich gestehe, obwohl es, wenn es meine Schuld ist, nur teilweise ist. Ich gebe zu, ich lebe nach den Ideen der parallelen Programmierung, die in den 80er Jahren des letzten Jahrhunderts entstanden sind. Antike? Könnte sein. Aber sagen Sie mir, was es Neues gibt, ĂŒber das die Wissenschaft der [parallelen] Programmierung damals noch nicht bekannt wĂ€re (siehe Details [1]). Zu dieser Zeit wurden parallele Programme in zwei Klassen unterteilt - parallel-seriell und asynchron. Wenn die ersteren bereits als archaisch angesehen wurden, dann die letzteren - fortgeschritten und wirklich parallel. Unter den letzteren wurden die Programmierung mit Ereignissteuerung (oder nur Ereignisprogrammierung), die Streamsteuerung und die dynamische Programmierung herausgegriffen.Das ist alles im Allgemeinen. Weitere Details bereits.

Und was bietet die aktuelle Programmierung zusĂ€tzlich zu dem, was bereits vor mindestens 40 Jahren bekannt war? In meinem "erfrorenen Blick" - nichts. Wie sich herausstellte, werden Coroutinen jetzt Coroutinen oder sogar Goroutinen genannt, und die Begriffe ParallelitĂ€t und Wettbewerb scheinen nicht nur Übersetzer zu betĂ€uben. Und solche Beispiele gibt es nicht. Was ist beispielsweise der Unterschied zwischen reaktiver Programmierung (RP) und Ereignisprogrammierung oder Streaming? In welche der bekannten Kategorien und / oder Klassifikationen fĂ€llt es? Niemand scheint daran interessiert zu sein, und niemand kann dies klarstellen. Oder können Sie jetzt nach Namen klassifizieren? Dann sind Coroutinen und Coroutinen in der Tat verschiedene Dinge, und die parallele Programmierung muss sich einfach von der Konkurrenzprogrammierung unterscheiden. Was ist mit Zustandsautomaten? Was fĂŒr eine Wundertechnik ist das?

Die „Spaghetti“ im Kopf entstehen aus dem Vergessen der Theorie, dass ein neues Modell bei der EinfĂŒhrung mit bereits bekannten und gut untersuchten Modellen verglichen wird. Ob dies gut gemacht wird, können Sie zumindest herausfinden, da der Prozess formalisiert ist. Aber wie kommt man dem auf den Grund, wenn man den Coroutinen einen neuen Spitznamen gibt und dann gleichzeitig den „Motorhaubencode“ in fĂŒnf Sprachen auswĂ€hlt und zusĂ€tzlich die Aussicht auf eine Migration in Streams bewertet. Und dies sind nur Coroutinen, die offen gesagt aufgrund ihrer elementaren Natur und ihres geringen Gebrauchs bereits vergessen werden sollten (es geht natĂŒrlich um meine Erfahrung).

2. Reaktive Programmierung und alles, alles, alles


Wir werden uns nicht das Ziel setzen, das Konzept der „reaktiven Programmierung“ grĂŒndlich zu verstehen, obwohl wir das „reaktive Beispiel“ als Grundlage fĂŒr weitere Diskussionen nehmen werden. Sein formales Modell wird auf Basis des bekannten formalen Modells erstellt. Ich hoffe, dies ermöglicht es uns, die Interpretation und Funktionsweise des ursprĂŒnglichen Programms klar, genau und detailliert zu verstehen. Inwieweit das erstellte Modell und seine Implementierung „reaktiv“ sein werden, entscheiden die Apologeten dieser Art der Programmierung. Im Moment wird es ausreichen, dass das neue Modell alle Nuancen des ursprĂŒnglichen Beispiels implementieren / modellieren muss. Wenn etwas nicht berĂŒcksichtigt wird, dann hoffe ich, dass es diejenigen gibt, die mich korrigieren.

In [2] wurde daher ein Beispiel fĂŒr ein reaktives Programm betrachtet, dessen Code in Listing 1 dargestellt ist.

Listing 1. Reaktiver Programmcode
1. 1 = 2 
2. 2 = 3 
3. 3 = 1 + 2 
4.  1, 2, 3 
5. 1 = 4 
6.  1, 2, 3


In der Welt der reaktiven Programmierung unterscheidet sich das Ergebnis seiner Arbeit vom Ergebnis eines regulĂ€ren Programms derselben Art. Das allein ist schlecht, wenn nicht hĂ€sslich, weil Das Ergebnis des Programms sollte eindeutig sein und nicht von der Umsetzung abhĂ€ngen. Aber mehr verwirrt den anderen. Erstens ist es anscheinend kaum möglich, einen regulĂ€ren Ă€hnlichen Code von einem reaktiven zu unterscheiden. Zweitens ist sich der Autor anscheinend der Arbeit des reaktiven Programms nicht ganz sicher und spricht ĂŒber das Ergebnis „höchstwahrscheinlich“. Und drittens, welches der Ergebnisse wird als richtig angesehen?

Eine solche Mehrdeutigkeit bei der Auslegung des Codes hat dazu gefĂŒhrt, dass es nicht sofort möglich ist, ihn zu „schneiden“. Aber dann, wie so oft, stellte sich heraus, dass alles viel einfacher war, als man erwartet hĂ€tte. Abbildung 1 zeigt zwei Strukturdiagramme, die hoffentlich der Struktur entsprechen und die Funktionsweise des Beispiels erlĂ€utern. Im oberen Diagramm organisieren die Blöcke X1 und X2 die Dateneingabe und signalisieren dem Block X3 ihre Änderung. Letzterer fĂŒhrt die Summierung durch und ermöglicht es dem Pr-Block, die aktuellen Werte der Variablen zu drucken. Nach dem Drucken signalisiert der Pr-Block dem X3-Block darĂŒber hinaus ihm und nur ihm, dass er bereit ist, neue Werte zu drucken.

Feige. 1. Zwei Strukturmodelle des Beispiels
image

Das zweite Schema ist im Vergleich zum ersten ziemlich elementar. Als Teil eines einzelnen Blocks werden Daten eingegeben und nacheinander implementiert: 1) Berechnen der Summe der Eingabedaten und 2) Drucken dieser Daten. Die interne FĂŒllung des Blocks auf dieser PrĂ€sentationsebene wird nicht offenbart. Obwohl gesagt werden kann, dass es sich auf struktureller Ebene um eine "Black Box mit einem Vier-Block-Schema" handeln kann. Trotzdem soll sein [algorithmisches] GerĂ€t anders sein.

Kommentar. Die Herangehensweise an das Programm als Black Box spiegelt im Wesentlichen die Einstellung des Benutzers dazu wider. Letzterer interessiert sich nicht fĂŒr die Umsetzung, sondern fĂŒr das Ergebnis der Arbeit. Ob es sich um ein reaktives Programm, ein Ereignisprogramm oder ein anderes handelt, aber das Ergebnis gemĂ€ĂŸ der Theorie der Algorithmen sollte eindeutig und vorhersehbar sein.

In Abb. 2 prÀsentiert algorithmische Modelle, die die interne [algorithmische] Struktur von Schaltungsblöcken im Detail verdeutlichen. Das obere Modell wird durch ein Netzwerk von Automaten dargestellt, wobei jede der Automaten ein algorithmisches Modell eines separaten Blocks ist. Die Verbindungen zwischen den durch strichpunktierte Bögen dargestellten Automaten entsprechen den Verbindungen der Schaltung. Ein Einzelautomatenmodell beschreibt den Betriebsalgorithmus eines Blockdiagramms, das aus einem Block besteht (siehe einen separaten Pr-Block in Abb. 1).

Feige. 2. Algorithmische Modelle fĂŒr Strukturschemata
image

Die Automaten X1 und X2 (die Namen der Automaten und Blöcke stimmen mit den Namen ihrer Variablen ĂŒberein) erkennen die Änderungen und gehen, wenn der Automat X3 bereit ist, die Additionsoperation auszufĂŒhren (im Zustand "s0"), in den Zustand "s1", wobei der aktuelle Wert der Variablen gespeichert wird. Das X3-GerĂ€t, das die Berechtigung zum Eingeben des Status "s1" erhalten hat, fĂŒhrt die Additionsoperation aus und wartet bei Bedarf auf den Abschluss des Druckvorgangs der Variablen. „Druckmaschine“ Pr kehrt nach Beendigung des Druckvorgangs in den Ausgangszustand „p0“ zurĂŒck und wartet auf den nĂ€chsten Befehl. Beachten Sie, dass sein Zustand "p1" eine Kette von umgekehrten ÜbergĂ€ngen startet - der Automat X3 in den Zustand "s0" und X1 und X2 in den Zustand "s0". Danach wird die Analyse der Eingabedaten, deren Summierung und anschließendes Drucken wiederholt.

Im Vergleich zum Automaten-Netzwerk ist der Algorithmus eines separaten Pr-Automaten recht einfach, aber wir stellen fest, dass er den gleichen Job macht und vielleicht sogar noch schneller. Seine PrĂ€dikate zeigen eine Änderung der Variablen. In diesem Fall erfolgt der Übergang in den Zustand „p1“ mit dem Start der Aktion y1 (siehe Abb. 2), die die aktuellen Werte der Variablen zusammenfasst, wĂ€hrend sie sich merken. Bei einem bedingungslosen Übergang vom Zustand "p1" zum Zustand "p0" druckt die Aktion y2 die Variablen. Danach kehrt der Prozess zur Analyse der Eingabedaten zurĂŒck. Der Implementierungscode fĂŒr das neueste Modell ist in Listing 2 dargestellt.

Listing 2. Implementierung des Pr-Automaten
#include "lfsaappl.h"
#include "fsynch.h"
extern LArc TBL_PlusX3[];
class FPlusX3 : public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FPlusX3(nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();

    FPlusX3(string strNam, CVarFsaLibrary *pCVFL): LFsaAppl(TBL_PlusX3, strNam, nullptr, pCVFL) { }

    CVar *pVarY;        		// 
    CVar *pVarX1;        		// 
    CVar *pVarX2;        		// 
    CVar *pVarX3;        		// 
    CVar *pVarStrNameX1;		//   X1
    CVar *pVarStrNameX2;		//   X2
    CVar *pVarStrNameX3;		//   X3
protected:
    int x1(); int x2();
    int x12() { return pVarX1 != nullptr && pVarX2 && pVarX3; };
    void y1();
    void y12() { FInit(); };
    double dSaveX1{0};
    double dSaveX2{0};
};

#include "stdafx.h"
#include "fplusx3.h"

LArc TBL_PlusX3[] = {
    LArc("st",		"st","^x12","y12"), 		//
    LArc("st",		"p0","x12",	"--"),			//
    LArc("p0",		"p1","x1",  "y1"),			//
    LArc("p0",		"p1","x2",  "y1"),			//
    LArc("p1",		"p0","--",  "--"),			//
    LArc()
};

// creating local variables and initialization of pointers
bool FPlusX3::FCreationOfLinksForVariables() {
// creating local variables
    pVarY = CreateLocVar("strY", CLocVar::vtString, "print of output string");			//  
    pVarX1 = CreateLocVar("dX1", CLocVar::vtDouble, "");			//  
    pVarX2 = CreateLocVar("dX2", CLocVar::vtDouble, "");			//  
    pVarX3 = CreateLocVar("dX3", CLocVar::vtDouble, "");			//  
    pVarStrNameX1 = CreateLocVar("strNameX1", CLocVar::vtString, "");			//   
    pVarStrNameX2 = CreateLocVar("strNameX2", CLocVar::vtString, "");			//   
    pVarStrNameX3 = CreateLocVar("strNameX3", CLocVar::vtString, "");			//   
// initialization of pointers
    string str;
    str = pVarStrNameX1->strGetDataSrc();
    if (str != "") { pVarX1 = pTAppCore->GetAddressVar(str.c_str(), this);	}
    str = pVarStrNameX2->strGetDataSrc();
    if (str != "") { pVarX2 = pTAppCore->GetAddressVar(str.c_str(), this);	}
    str = pVarStrNameX3->strGetDataSrc();
    if (str != "") { pVarX3 = pTAppCore->GetAddressVar(str.c_str(), this);	}
    return true;
}

int FPlusX3::x1() { return pVarX1->GetDataSrc() != dSaveX1; }
int FPlusX3::x2() { return pVarX2->GetDataSrc() != dSaveX2; }

void FPlusX3::y1() {
// X3 = X1 + X2
    double dX1 = pVarX1->GetDataSrc(); double dX2 = pVarX2->GetDataSrc();
    double dX3 = dX1 + dX2;
    pVarX3->SetDataSrc(this, dX3);
    dSaveX1 = dX1; dSaveX2 = dX2;
//  1, 2, 3
    QString strX1; strX1.setNum(dX1); QString strX2; strX2.setNum(dX2);
    QString strX3; strX3.setNum(dX3);
    QString qstr = "X1=" + strX1 + ", X2=" + strX2 + ", X3=" + strX3;
    pVarY->SetDataSrc(nullptr, qstr.toStdString(), nullptr);
}


Die Codemenge ist eindeutig unvergleichlich grĂ¶ĂŸer als das ursprĂŒngliche Beispiel. Beachten Sie jedoch, dass es keinen einzigen Code gibt. Die neue Lösung beseitigt alle Funktionsprobleme und lĂ€sst bei der Interpretation des Programms keine Fantasien zu. Ein Beispiel, das kompakt und elegant aussieht, von dem man aber "höchstwahrscheinlich" sagen kann, verursacht beispielsweise keine positiven Emotionen und den Wunsch, damit zu arbeiten. Es sollte auch beachtet werden, dass es notwendig ist, tatsĂ€chlich mit der Wirkung des Automaten y1 zu vergleichen.

Der Rest des Codes bezieht sich auf die Anforderungen der "automatischen Umgebung", die, wie ich bemerke, im Quellcode nicht gesprochen wird. Also, die FCreationOfLinksForVariables Methode der Basis Automaten Klasse LFsaApplErstellt lokale Variablen fĂŒr den Computer und verknĂŒpft sie, wenn auf der Ebene der VKPA-Umgebung symbolische Namen der anderen ihnen zugeordneten Umgebungsvariablen angezeigt werden. Das erste Mal beginnt es beim Erstellen eines Automaten und dann im Rahmen der FInit- Methode (siehe Schritt y12), weil Beim Erstellen eines Objekts sind nicht alle Links bekannt. Die Maschine befindet sich im Status "st", bis alle erforderlichen VerknĂŒpfungen, die die x12-PrĂ€dikatprĂŒfungen durchfĂŒhren, initialisiert wurden. Ein Verweis auf eine Variable gibt, wenn ihr Name angegeben wird, die GetAddressVar-Methode zurĂŒck.

Um mögliche Fragen zu entfernen, prĂ€sentieren wir den Code des Automaten-Netzwerks. Es ist in Listing 3 dargestellt und enthĂ€lt den Code fĂŒr drei Automatenklassen. Auf ihrer Grundlage werden viele Objekte erstellt, die dem in Abb. 1 gezeigten Strukturdiagramm des Netzwerks entsprechen. 1. Beachten Sie, dass die Objekte X1 und X2 von der allgemeinen Klasse FSynch abgeleitet sind.

Listing 3. Automatisierte Netzwerkklassen
#include "lfsaappl.h"

extern LArc TBL_Synch[];
class FSynch : public LFsaAppl
{
public:
    double dGetData() { return pVarX->GetDataSrc(); };
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FSynch(nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();

    FSynch(string strNam, CVarFsaLibrary *pCVFL): LFsaAppl(TBL_Synch, strNam, nullptr, pCVFL) { }

    CVar *pVarX;			// 
    CVar *pVarStrNameX;		//   
    CVar *pVarStrNameObject;//  -
    LFsaAppl *pL {nullptr};
protected:
    int x1() { return pVarX->GetDataSrc() != dSaveX; }
    int x2() { return pL->FGetState() == "s1"; }
    int x12() { return pL != nullptr; };
    void y1() { dSaveX = pVarX->GetDataSrc(); }
    void y12() { FInit(); };
    double dSaveX{0};
};

#include "stdafx.h"
#include "fsynch.h"

LArc TBL_Synch[] = {
    LArc("st",		"st","^x12","y12"), 		//
    LArc("st",		"s0","x12",	"y1"),			//
    LArc("s0",		"s1","x1",  "y1"),			//
    LArc("s1",		"s0","x2",	"--"),			//
    LArc()
};

// creating local variables and initialization of pointers
bool FSynch::FCreationOfLinksForVariables() {
// creating local variables
    pVarX = CreateLocVar("x", CLocVar::vtDouble, " ");
    pVarStrNameX = CreateLocVar("strNameX1", CLocVar::vtString, "name of external input variable(x1)");			//   
    pVarStrNameObject = CreateLocVar("strNameObject", CLocVar::vtString, "name of function");                   //  
// initialization of pointers
    string str;
    if (pVarStrNameX) {
        str = pVarStrNameX->strGetDataSrc();
        if (str != "") { pVarX = pTAppCore->GetAddressVar(str.c_str(), this);	}
    }
    str = pVarStrNameObject->strGetDataSrc();
    if (str != "") { pL = FGetPtrFsaAppl(str);	}
    return true;
}

#include "lfsaappl.h"
#include "fsynch.h"

extern LArc TBL_X1X2X3[];
class FX1X2X3 : public LFsaAppl
{
public:
    double dGetData() { return pVarX3->GetDataSrc(); };
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FX1X2X3(nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();

    FX1X2X3(string strNam, CVarFsaLibrary *pCVFL): LFsaAppl(TBL_X1X2X3, strNam, nullptr, pCVFL) { }

    CVar *pVarX1{nullptr};			//
    CVar *pVarX2{nullptr};			//
    CVar *pVarX3{nullptr};			//
    CVar *pVarStrNameFX1;		//  X1
    CVar *pVarStrNameFX2;		//  X2
    CVar *pVarStrNameFPr;		//  Pr
    CVar *pVarStrNameX3;		//   
    FSynch *pLX1 {nullptr};
    FSynch *pLX2 {nullptr};
    LFsaAppl *pLPr {nullptr};
protected:
    int x1() { return pLX1->FGetState() == "s1"; }
    int x2() { return pLX2->FGetState() == "s1"; }
    int x3() { return pLPr->FGetState() == "p1"; }
    int x12() { return pLPr != nullptr && pLX1 && pLX2 && pVarX3; };
    void y1() { pVarX3->SetDataSrc(this, pLX1->dGetData() + pLX2->dGetData()); }
    void y12() { FInit(); };
};
#include "stdafx.h"
#include "fx1x2x3.h"

LArc TBL_X1X2X3[] = {
    LArc("st",		"st","^x12","y12"), 		//
    LArc("st",		"s0","x12",	"--"),			//
    LArc("s0",		"s1","x1",  "y1"),			//
    LArc("s0",		"s1","x2",  "y1"),			//
    LArc("s1",		"s0","x3",	"--"),			//
    LArc()
};
// creating local variables and initialization of pointers
bool FX1X2X3::FCreationOfLinksForVariables() {
// creating local variables
    pVarX3 = CreateLocVar("x", CLocVar::vtDouble, " ");
    pVarStrNameFX1 = CreateLocVar("strNameFX1", CLocVar::vtString, "");
    pVarStrNameFX2 = CreateLocVar("strNameFX2", CLocVar::vtString, "");
    pVarStrNameFPr = CreateLocVar("strNameFPr", CLocVar::vtString, "");
    pVarStrNameX3 = CreateLocVar("strNameX3", CLocVar::vtString, "");
// initialization of pointers
    string str; str = pVarStrNameFX1->strGetDataSrc();
    if (str != "") { pLX1 = (FSynch*)FGetPtrFsaAppl(str);	}
    str = pVarStrNameFX2->strGetDataSrc();
    if (str != "") { pLX2 = (FSynch*)FGetPtrFsaAppl(str);	}
    str = pVarStrNameFPr->strGetDataSrc();
    if (str != "") { pLPr = FGetPtrFsaAppl(str);	}
    return true;
}
#include "lfsaappl.h"
#include "fsynch.h"

extern LArc TBL_Print[];
class FX1X2X3;
class FPrint : public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FPrint(nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();

    FPrint(string strNam, CVarFsaLibrary *pCVFL): LFsaAppl(TBL_Print, strNam, nullptr, pCVFL) { }

    CVar *pVarY;        		// 
    CVar *pVarStrNameFX1;		//    X1
    CVar *pVarStrNameFX2;		//    X2
    CVar *pVarStrNameFX3;		//    X3
    FSynch *pLX1 {nullptr};     //    X1
    FSynch *pLX2 {nullptr};     //    X2
    FX1X2X3 *pLX3 {nullptr};    //    X3
protected:
    int x1();
    int x12() { return pLX3 != nullptr && pLX1 && pLX2 && pLX3; };
    void y1();
    void y12() { FInit(); };
};
#include "stdafx.h"
#include "fprint.h"
#include "fx1x2x3.h"

LArc TBL_Print[] = {
    LArc("st",		"st","^x12","y12"), 		//
    LArc("st",		"p0","x12",	"--"),			//
    LArc("p0",		"p1","x1",  "y1"),			//
    LArc("p1",		"p0","--",	"--"),			//
    LArc()
};
// creating local variables and initialization of pointers
bool FPrint::FCreationOfLinksForVariables() {
// creating local variables
    pVarY = CreateLocVar("strY", CLocVar::vtString, "print of output string");			//  
    pVarStrNameFX1 = CreateLocVar("strNameFX1", CLocVar::vtString, "name of external input object(x1)");			//   
    pVarStrNameFX2 = CreateLocVar("strNameFX2", CLocVar::vtString, "name of external input object(x2)");			//   
    pVarStrNameFX3 = CreateLocVar("strNameFX3", CLocVar::vtString, "name of external input object(pr)");			//   
// initialization of pointers
    string str;
    str = pVarStrNameFX1->strGetDataSrc();
    if (str != "") { pLX1 = (FSynch*)FGetPtrFsaAppl(str);	}
    str = pVarStrNameFX2->strGetDataSrc();
    if (str != "") { pLX2 = (FSynch*)FGetPtrFsaAppl(str);	}
    str = pVarStrNameFX3->strGetDataSrc();
    if (str != "") { pLX3 = (FX1X2X3*)FGetPtrFsaAppl(str);	}
    return true;
}

int FPrint::x1() { return pLX3->FGetState() == "s1"; }

void FPrint::y1() {
    QString strX1; strX1.setNum(pLX1->dGetData());
    QString strX2; strX2.setNum(pLX2->dGetData());
    QString strX3; strX3.setNum(pLX3->dGetData());
    QString qstr = "X1=" + strX1 + ", X2=" + strX2 + ", X3=" + strX3;
    pVarY->SetDataSrc(nullptr, qstr.toStdString(), nullptr);
}


Dieser Code unterscheidet sich von Listing 1, wie ein Bild eines Flugzeugs aus der Konstruktionsdokumentation. Aber ich denke, wir sind in erster Linie Programmierer, und einige Designer werden ihnen nicht beleidigt. Unser "Design Code" sollte leicht zu verstehen und eindeutig zu interpretieren sein, damit unser "Flugzeug" beim ersten Flug nicht abstĂŒrzt. Und wenn ein solches UnglĂŒck passiert ist und dies bei Programmen hĂ€ufiger vorkommt als bei Flugzeugen, kann der Grund leicht und schnell gefunden werden.

In Anbetracht von Listing 3 mĂŒssen Sie sich daher vorstellen, dass die Anzahl der Klassen nicht direkt mit der Anzahl der entsprechenden Objekte im Parallelprogramm zusammenhĂ€ngt. Der Code spiegelt nicht die Beziehung zwischen Objekten wider, sondern enthĂ€lt die Mechanismen, mit denen sie erstellt werden. Die FSynch- Klasse enthĂ€lt also einen pL- Zeiger auf ein Objekt vom TypLFsaAppl . Der Name dieses Objekts wird durch eine lokale Variable bestimmt, die in der VKPa-Umgebung einer Automatenvariablen mit dem Namen strNameObject entspricht . Ein Zeiger ist erforderlich, um mit der FGetState- Methode den aktuellen Status eines Automatenobjekts vom Typ FSynch zu ĂŒberwachen (siehe PrĂ€dikatcode x2). Ähnliche Zeiger auf Objekte, Variablen zum Angeben der Namen von Objekten und PrĂ€dikate zum Organisieren von Beziehungen enthalten andere Klassen.

Nun ein paar Worte zum „Aufbau“ eines Parallelprogramms in der VKPA-Umgebung. Es wird beim Laden der Programmkonfiguration erstellt. In diesem Fall werden erste Objekte auf der Basis von Klassen aus thematischen dynamischen Bibliotheken eines Automatentyps erstellt (ihre Menge wird durch die Konfiguration der Anwendung / des Programms bestimmt). Erstellte Objekte werden anhand ihrer Namen identifiziert (nennen wir sie automatische Variablen) Anschließend werden die erforderlichen Werte in die lokalen Variablen der Automaten geschrieben. In unserem Fall werden Variablen mit einem Zeichenfolgentyp auf die Variablennamen anderer Objekte und / oder die Namen der Objekte gesetzt. Auf diese Weise werden Verbindungen zwischen Objekten eines Parallelautomatenprogramms hergestellt (siehe Abb. 1). Durch Ändern der Werte der Eingabevariablen (mithilfe einzelner Objektsteuerungsdialoge oder der Standarddialoge / Umgebungsdialoge zum Festlegen von Werten fĂŒr Umgebungsvariablen) wird das Ergebnis korrigiert. Es kann mithilfe eines Standardumgebungsdialogs angezeigt werden, um die Werte von Variablen anzuzeigen.

3. Zur Analyse paralleler Programme


Über die Funktionsweise eines Parallelprogramms ist es sehr, sehr schwierig, etwas Konkretes zu sagen, es sei denn, es ist ganz einfach sequentiell parallel. Das betrachtete Netzwerk von Automaten ist keine Ausnahme. Als nĂ€chstes werden wir dies sehen und verstehen, was von ihm erwartet werden kann.

Der resultierende Automat und das Netzwerk, fĂŒr das er aufgebaut ist, sind in Abb. 2 dargestellt. 3. Aus dem Netzwerk in Abb. 2 zeichnet sich neben der Umbenennung seiner Elemente - Automaten, Eingangs- und Ausgangssignale - durch das Fehlen einer „Druckmaschine“ von Variablen aus. Letzteres ist fĂŒr den Betrieb des Netzwerks nicht unbedingt erforderlich. Durch Umbenennen können Sie die Kompositionsoperation verwenden, um den resultierenden Automaten zu erstellen. Um kĂŒrzere Namen zu erstellen, wurde zusĂ€tzlich die Codierung eingefĂŒhrt, wenn beispielsweise der Zustand "a0" des Automaten A durch das Symbol "0" und "a1" durch das Symbol "1" dargestellt wird. Ähnliches gilt fĂŒr andere Maschinen. In diesem Fall wird dem Komponentenstatus des Netzwerks, z. B. "a1b0c1", der Name "101" zugewiesen. In Ă€hnlicher Weise werden Namen fĂŒr alle KomponentenzustĂ€nde des Netzwerks gebildet, deren Anzahl durch das Produkt der ZustĂ€nde von Komponentenautomaten bestimmt wird.

Feige. 3. Der resultierende Netzwerkautomat
image

Der resultierende Automat kann natĂŒrlich rein formal berechnet werden, aber dafĂŒr brauchen wir einen geeigneten „Taschenrechner“. Ist dies nicht der Fall, können Sie einen recht einfachen intuitiven Algorithmus verwenden. Innerhalb seines Rahmens wird der eine oder andere Komponentenzustand des Netzwerks aufgezeichnet, und dann werden die ZielkomponentenzustĂ€nde durch "Handles" bestimmt, indem alle möglichen Eingabesituationen sortiert werden. Nachdem also der Zustand "000" festgelegt wurde, der den aktuellen ZustĂ€nden der Komponentenautomaten - "a0", "b0", "c0" - entspricht, werden ÜbergĂ€nge fĂŒr Konjunktionen von Eingangsvariablen ^ x1 ^ x2, ^ x1x2, x1 ^ x2, x1x2 bestimmt. Wir erhalten die ÜbergĂ€nge jeweils in gibt "a0b0c0", "a0b1c0", "a1b0c0", "a1b1c0" an, die auf der resultierenden Maschine mit "000", "010", "100" und "110" gekennzeichnet sind. Schleifendie nicht mit Aktionen geladen sind, können aus dem Diagramm ausgeschlossen werden.

Was wir "im trockenen RĂŒckstand" haben. Wir haben die Hauptsache erreicht - wir haben den resultierenden Automaten erhalten, der den Betrieb des Netzwerks genau beschreibt. Wir haben herausgefunden, dass von acht möglichen NetzwerkzustĂ€nden einer nicht zugĂ€nglich (isoliert) ist - Zustand „001“. Dies bedeutet, dass die Summierungsoperation unter keinen UmstĂ€nden fĂŒr Eingabevariablen ausgelöst wird, die den aktuellen Wert nicht geĂ€ndert haben.

Was störend ist, obwohl das Testen keine Fehler ergab. In der Grafik des resultierenden Automaten wurden ÜbergĂ€nge gefunden, die bei Ausgabeaktionen in Konflikt stehen. Sie sind mit einer Kombination der Aktionen y1y3 und y2y3 gekennzeichnet. Die Aktionen y1 und y2 werden ausgelöst, wenn sich die Eingabedaten Ă€ndern, und dann berechnet eine andere Aktion y3 die Summe der Variablen parallel zu ihnen. Mit welchen Werten wird es arbeiten - alt oder nur durch neue geĂ€ndert? Um Mehrdeutigkeiten zu beseitigen, können Sie einfach die Aktionen von y3 und y4 Ă€ndern. In diesem Fall lautet ihr Code wie folgt: X3 = X1Sav + X2Sav und print (X1Sav, X2Sav, X3).

Damit. Die Konstruktion des resultierenden Automaten ergab offensichtliche Probleme im erstellten Parallelmodell. Ob sie im reaktiven Programm erscheinen, ist eine Frage. Alles wird anscheinend von der Herangehensweise an die Umsetzung der ParallelitĂ€t im reaktiven Paradigma abhĂ€ngen. In jedem Fall muss eine solche AbhĂ€ngigkeit berĂŒcksichtigt und irgendwie beseitigt werden. Bei einem automatisierten Netzwerk ist es einfacher, die geĂ€nderte Version zu verlassen, als zu versuchen, das Netzwerk zu Ă€ndern. Es ist in Ordnung, wenn zuerst die "alten" Daten gedruckt werden, die den Netzwerkbetrieb initiiert haben, und dann die aktuellen Daten als nĂ€chstes gedruckt werden.

4. Schlussfolgerung


Jede der betrachteten Lösungen hat ihre Vor- und Nachteile. Das erste ist sehr einfach, das Netzwerk ist komplizierter und wird auf der Basis einer einzelnen Maschine erstellt. Erst nach der Visualisierung werden die Eingabedaten analysiert. Aufgrund seiner ParallelitĂ€t startet dasselbe automatische Netzwerk die Analyse der Eingabedaten vor dem Ende des Druckvorgangs. Und wenn die Visualisierungszeit lang ist, dies jedoch bei der Summierungsoperation der Fall ist, ist das Netzwerk unter dem Gesichtspunkt der Eingabesteuerung schneller. Jene. Eine Bewertung auf der Grundlage einer SchĂ€tzung der Code-Menge bei parallelen Programmen ist nicht immer objektiv. Einfacher ausgedrĂŒckt ist das Netzwerk parallel, die Einkomponentenlösung ist weitgehend sequentiell (ihre PrĂ€dikate und Aktionen sind parallel). Und wir sprechen zunĂ€chst von parallelen Programmen.

Das Netzwerkmodell ist auch ein Beispiel fĂŒr eine flexible Lösung. Erstens können Komponenten unabhĂ€ngig voneinander entworfen werden. Zweitens kann jede Komponente durch eine andere ersetzt werden. Und drittens kann jede Netzwerkkomponente ein Element einer Bibliothek automatischer Prozesse sein und wird in einer anderen Netzwerklösung verwendet. Und dies sind nur die offensichtlichsten Vorteile einer parallelen Lösung.

Aber zurĂŒck zur reaktiven Programmierung. Betrachtet RP alle Programmanweisungen zunĂ€chst als parallel? Wir können nur davon ausgehen, dass es ohne dies schwierig ist, ĂŒber ein Programmierparadigma zu sprechen, das „auf DatenflĂŒsse und die Ausbreitung von Änderungen ausgerichtet ist“ (siehe die Definition der reaktiven Programmierung in [3]). Aber was ist dann der Unterschied zur Programmierung mit Streaming-Steuerung (fĂŒr weitere Details siehe [1])? Wir kehren also zu unserem Ausgangspunkt zurĂŒck: Wie klassifiziert man reaktive Programmierung im Rahmen bekannter Klassifikationen? Und wenn RP etwas Besonderes ist, was unterscheidet es dann von den bekannten Programmierparadigmen?

Nun, ĂŒber die Theorie. Ohne sie wĂ€re die Analyse paralleler Algorithmen nicht nur schwierig - unmöglich. Der Analyseprozess zeigt manchmal Probleme auf, die selbst bei einem sorgfĂ€ltigen und nachdenklichen Blick auf das Programm, wie ĂŒbrigens auf das „Designdokument“, nicht zu erraten sind. Auf jeden Fall bin ich dafĂŒr, dass Flugzeuge sowohl im ĂŒbertragenen als auch im anderen Sinne nicht abstĂŒrzen. Dies ist fĂŒr mich die Tatsache, dass Sie natĂŒrlich nach Einfachheit und Anmut der Form streben mĂŒssen, aber ohne QualitĂ€tsverlust. Wir Programmierer „zeichnen“ nicht nur Programme, sondern kontrollieren oft, was dort verborgen ist, auch durch Flugzeuge!

Ja, ich hĂ€tte es fast vergessen. Ich wĂŒrde die automatische Programmierung (AP) als Programmierung mit dynamischer Steuerung klassifizieren. Was die AsynchronitĂ€t betrifft - ich wette. Vorausgesetzt, dass die Basis des AP-Steuerungsmodells ein Netzwerk in einer einzigen Zeit ist, d.h. synchrone Netzwerke von Automaten, dann ist es synchron. Da die VKPa-Umgebung jedoch auch viele Netzwerke durch das Konzept der „Automatenwelten“ implementiert, ist sie vollstĂ€ndig asynchron. Im Allgemeinen bin ich gegen einen sehr starren Klassifizierungsrahmen, aber nicht fĂŒr Anarchie. In diesem Sinne hoffe ich, dass in VKPa ein gewisser Kompromiss zwischen der Starrheit der seriell-parallelen Programmierung und einem gewissen asynchronen Anarchismus erzielt wurde. Angesichts der Tatsache, dass die automatische Programmierung auch die Klasse der Ereignisprogramme abdeckt (siehe [4]) und Stream-Programme darin leicht modelliert werden können,Von welcher Programmierung kannst du noch trĂ€umen? Sicher - fĂŒr mich.

Literatur
1. /.. , .. , .. , .. ; . .. . – .: , 1983. – 240.
2. . [ ], : habr.com/ru/post/486632 . . . ( 07.02.2020).
3. . . [ ], : ru.wikipedia.org/wiki/_ . . . ( 07.02.2020).
4. — ? [ ], : habr.com/ru/post/483610 . . . ( 07.02.2020).

Source: https://habr.com/ru/post/undefined/


All Articles