Sobre el corutinismo competitivo (usando la programación reactiva como ejemplo)

1. Introducción


La competencia por las mentes, los estados de ánimo y las aspiraciones de los programadores es, como me parece, una tendencia moderna en el desarrollo de la programación. Cuando casi no se propone nada, aunque bajo el lema de la lucha por ello. Es muy, muy difícil reconocer en la avalancha de paradigmas de software algo nuevo, que de hecho resulta ser bastante conocido y, a veces, simplemente obsoleto. Todo está "lavado" por delicias terminológicas, análisis detallado y ejemplos multilínea en muchos lenguajes de programación. Al mismo tiempo, las solicitudes para abrir y / o considerar el trasfondo de la solución, la esencia de las innovaciones se evitan obstinadamente, los intentos de averiguar cuánto se necesita y qué proporcionará al final, que distingue cualitativamente la innovación de los enfoques y herramientas de programación ya conocidos, se frustran de raíz.

Aparecí en Habré, como se notó acertadamente en una de las discusiones, después de un cierto congelamiento. Ni siquiera me importará. Al menos, la impresión, aparentemente, es solo eso. Por lo tanto, estoy de acuerdo, lo confieso, aunque, si es mi culpa, es solo parcialmente. Lo admito, vivo por las ideas sobre programación paralela, formada en los años 80 del siglo pasado. ¿Antigüedad? Tal vez. Pero dígame qué hay de nuevo, sobre lo cual la ciencia de la programación [paralela] aún no se conocería en ese momento (ver detalles [1]). En ese momento, los programas paralelos se dividían en dos clases: paralelo-serie y asíncrono. Si los primeros ya se consideraban arcaicos, entonces los segundos, avanzados y verdaderamente paralelos. Entre estos últimos, se destacó la programación con control de eventos (o solo programación de eventos), control de flujo y programación dinámica.Eso es todo en general. Más detalles ya.

¿Y qué ofrece la programación actual además de lo que ya se conoce al menos hace 40 años? En mi "mirada congelada", nada. Resultó que las corutinas ahora se llaman corutinas o incluso gorutinas; los términos concurrencia y competencia entran en un estupor, al parecer, no solo traductores. Y no hay tales ejemplos. Por ejemplo, ¿cuál es la diferencia entre la programación reactiva (RP) y la programación o transmisión de eventos? ¿En cuál de las categorías y / o clasificaciones conocidas se incluye? Nadie parece estar interesado en esto, y nadie puede aclarar esto. ¿O puedes clasificar ahora por nombre? Entonces, de hecho, las corutinas y las corutinas son cosas diferentes, y la programación paralela simplemente está obligada a diferir de la competitiva. ¿Qué pasa con las máquinas de estado? ¿Qué tipo de técnica milagrosa es esta?

El "espagueti" en la cabeza surge del olvido de una teoría en la que, cuando se introduce un nuevo modelo, se compara con modelos ya conocidos y bien estudiados. Si esto se hará bien, pero al menos puede averiguarlo, porque el proceso está formalizado. Pero, ¿cómo llegar al fondo del asunto si las corutinas reciben un nuevo apodo y luego seleccionan el "código de capó" simultáneamente en cinco idiomas, evaluando además la posibilidad de migración a las transmisiones. Y estas son solo corutinas, que, para ser honesto, ya deberían olvidarse debido a su naturaleza elemental y su pequeño uso (se trata, por supuesto, de mi experiencia).

2. Programación reactiva y todo, todo, todo.


No nos fijaremos el objetivo de comprender a fondo el concepto de "programación reactiva", aunque tomaremos el "ejemplo reactivo" como base para una mayor discusión. Su modelo formal se creará sobre la base del conocido modelo formal. Y esto, espero, nos permitirá comprender de manera clara, precisa y detallada la interpretación y el funcionamiento del programa original. Pero cuánto decidirán el modelo creado y su implementación será "reactivo". Por el momento, será suficiente por ahora que el nuevo modelo tendrá que implementar / modelar todos los matices del ejemplo original. Si algo no se tiene en cuenta, espero que haya quienes me corrijan.

Entonces, en [2], se consideró un ejemplo de un programa reactivo, cuyo código se muestra en el Listado 1.

Listado 1. Código de programa reactivo
1. 1 = 2 
2. 2 = 3 
3. 3 = 1 + 2 
4.  1, 2, 3 
5. 1 = 4 
6.  1, 2, 3


En el mundo de la programación reactiva, el resultado de su trabajo será diferente del resultado de un programa regular del mismo tipo. Esto solo es malo, por no decir fealdad, porque El resultado del programa debe ser inequívoco y no depender de la implementación. Pero más confunde al otro. En primer lugar, en apariencia es casi imposible distinguir un código similar regular de uno reactivo. En segundo lugar, aparentemente, el autor mismo no está completamente seguro del trabajo del programa reactivo, y habla del resultado "más probable". Y en tercer lugar, ¿cuál de los resultados se considera correcto?

Tal ambigüedad en la interpretación del código ha llevado al hecho de que no es posible "cortarlo" de inmediato. Pero luego, como sucede a menudo, todo resultó ser mucho más simple de lo que uno podría haber esperado. La Figura 1 muestra dos diagramas estructurales que, con suerte, corresponden a la estructura y explican el funcionamiento del ejemplo. En el diagrama superior, los bloques X1 y X2 organizan la entrada de datos, señalando al bloque X3 sobre su cambio. Este último realiza la suma y permite que el bloque Pr imprima los valores actuales de las variables. Una vez impreso, el bloque Pr le indica al bloque X3, además, a él y solo a él que está listo para imprimir nuevos valores.

Higo. 1. Dos modelos estructurales del ejemplo.
image

El segundo esquema, en comparación con el primero, es bastante elemental. Como parte de un solo bloque, ingresa datos e implementa secuencialmente: 1) calcular la suma de los datos de entrada y 2) imprimirlos. El relleno interno del bloque en este nivel de presentación no se revela. Aunque se puede decir que a nivel estructural puede ser una "caja negra que incluye un esquema de cuatro bloques". Pero aún así, se supone que su dispositivo [algorítmico] es diferente.

Comentario. El enfoque del programa como un cuadro negro refleja esencialmente la actitud del usuario hacia él. Este último no está interesado en su implementación, sino en el resultado del trabajo. Si se trata de un programa reactivo, un programa de eventos o algún otro, pero el resultado de acuerdo con la teoría de los algoritmos debe ser inequívoco y predecible.

En la Fig. 2 presenta modelos algorítmicos que aclaran en detalle la estructura interna [algorítmica] de los bloques de circuitos. El modelo superior está representado por una red de autómatas, donde cada autómata es un modelo algorítmico de un bloque separado. Las conexiones entre los autómatas que se muestran mediante arcos con puntos de guión corresponden a las conexiones del circuito. Un modelo de autómata simple describe el algoritmo de operación de un diagrama de bloques que consiste en un bloque (vea un bloque Pr separado en la Fig. 1).

Higo. 2. Modelos algorítmicos para esquemas estructurales.
image

Los autómatas X1 y X2 (los nombres de los autómatas y los bloques coinciden con los nombres de sus variables), detectan los cambios y, si el autómata X3 está listo para realizar la operación de suma (en el estado "s0"), ingresan al estado "s1", recordando el valor actual de la variable. La máquina X3, después de haber recibido permiso para ingresar al estado "s1", realiza la operación de adición y, si es necesario, espera la finalización de la impresión de las variables. “La máquina de impresión“ Pr, una vez finalizada la impresión, vuelve al estado inicial “p0”, donde espera el siguiente comando. Tenga en cuenta que su estado "p1" inicia una cadena de transiciones inversas: el autómata X3 al estado "s0" y X1 y X2 al estado "s0". Después de eso, se repite el análisis de los datos de entrada, luego su suma y posterior impresión.

En comparación con la red de autómatas, el algoritmo de un autómata Pr separado es bastante simple, pero, notamos, hace el mismo trabajo y quizás incluso más rápido. Sus predicados revelan un cambio en las variables. Si esto sucede, la transición al estado "p1" se realiza con el inicio de la acción y1 (ver Fig. 2), que resume los valores actuales de las variables, mientras las recuerda. Luego, en una transición incondicional del estado "p1" al estado "p0", la acción y2 imprime las variables. Después de eso, el proceso vuelve al análisis de los datos de entrada. El código de implementación para el último modelo se muestra en el Listado 2.

Listado 2. Implementación del autómata Pr
#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);
}


La cantidad de código es claramente incomparablemente mayor que el ejemplo original. Pero, tenga en cuenta, ni un solo código. La nueva solución elimina todos los problemas de funcionamiento, no permitiendo que se formen fantasías en la interpretación del programa. Un ejemplo que se ve compacto y elegante, pero sobre el que se puede decir "muy probablemente", no causa, digamos, emociones positivas y un deseo de trabajar con él. También se debe tener en cuenta que es necesario comparar realmente con la acción del autómata y1.

El resto del código está relacionado con los requisitos del "entorno automático", que, observo, no se menciona en el código fuente. Entonces, el método FCreationOfLinksForVariables de la clase de autómata base LFsaApplcrea variables locales para la máquina y las vincula cuando se indican al nivel del entorno VKPA los nombres simbólicos de las otras variables de entorno asociadas con ellos. La primera vez que se inicia al crear un autómata, y luego en el marco del método FInit (consulte el paso y12), porque No todos los enlaces son conocidos al crear un objeto. La máquina estará en el estado "st" hasta que se inicialicen todos los enlaces necesarios que comprueban el predicado x12. Una referencia a una variable, si se le da su nombre, devuelve el método GetAddressVar.

Para eliminar posibles preguntas, presentamos el código de la red de autómatas. Se muestra en el Listado 3 e incluye el código para tres clases de autómatas. Es sobre su base que se crean muchos objetos que corresponden al diagrama estructural de la red que se muestra en la Fig. 1. Tenga en cuenta que los objetos X1 y X2 se derivan de la clase general FSynch.

Listado 3. Clases de red automatizadas
#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);
}


Este código es diferente del Listado 1, como una imagen de un avión de su documentación de diseño. Pero, creo, somos principalmente programadores, y, sin ofender, algunos diseñadores. Nuestro "código de diseño" debe ser fácil de entender e interpretar sin ambigüedades para que nuestro "avión" no se estrelle en el primer vuelo. Y si sucedió tal desgracia, y con los programas esto ocurre con más frecuencia que con los aviones, entonces la razón se puede encontrar fácil y rápidamente.

Por lo tanto, considerando el Listado 3, debe imaginar que el número de clases no está directamente relacionado con el número de objetos correspondientes en el programa paralelo. El código no refleja la relación entre los objetos, pero contiene los mecanismos que los crean. Entonces, la clase FSynch contiene un puntero pL a un objeto de tipoLFsaAppl . El nombre de este objeto está determinado por una variable local, que en el entorno VKPa corresponderá a una variable de autómata con el nombre strNameObject . Es necesario un puntero para usar el método FGetState para monitorear el estado actual de un objeto de autómata de tipo FSynch (consulte el código de predicado x2). Los punteros similares a los objetos, las variables para especificar los nombres de los objetos y los predicados necesarios para organizar las relaciones contienen otras clases.

Ahora algunas palabras sobre la "construcción" de un programa paralelo en el entorno VKPA. Se crea durante la carga de la configuración del programa. En este caso, los primeros objetos se crean sobre la base de clases de bibliotecas dinámicas temáticas de tipo autómata (su conjunto está determinado por la configuración de la aplicación / programa). Los objetos creados se identifican por sus nombres (llamémosles variables automáticas) Luego, los valores necesarios se escriben en las variables locales de los autómatas. En nuestro caso, las variables con un tipo de cadena se configuran con los nombres de las variables de otros objetos y / o los nombres de los objetos. De esta manera, se establecen conexiones entre objetos de un programa de autómatas paralelos (ver Fig. 1). Además, al cambiar los valores de las variables de entrada (mediante el uso de cuadros de diálogo de control de objetos individuales o los cuadros de diálogo estándar de diálogo / entorno para establecer valores para las variables de entorno), fijamos el resultado. Se puede ver usando un diálogo de entorno estándar para mostrar los valores de las variables.

3. Al análisis de programas paralelos.


Sobre el funcionamiento de un programa paralelo, a menos que sea bastante simple secuencialmente paralelo, es muy, muy difícil decir algo concreto. La red considerada de autómatas no es una excepción. A continuación, veremos esto, entendiendo lo que se puede esperar de él.

El autómata resultante y la red para la cual está construido se muestran en la Fig. 3. Desde la red en la Fig. 2, además de renombrar sus elementos: autómatas, señales de entrada y salida, se distingue por la ausencia de una "máquina de impresión" de variables. Esto último no es esencial para el funcionamiento de la red, y el cambio de nombre le permite utilizar la operación de composición para construir el autómata resultante. Además, para crear nombres más cortos, se introdujo la codificación cuando, por ejemplo, el estado "a0" del autómata A está representado por el símbolo "0" y "a1" por el símbolo "1". Del mismo modo para otras máquinas. En este caso, al estado componente de la red, por ejemplo, "a1b0c1", se le asigna el nombre "101". Del mismo modo, los nombres se forman para todos los estados componentes de la red, cuyo número está determinado por el producto de los estados de los autómatas componentes.

Higo. 3. El autómata de red resultante
image

El autómata resultante, por supuesto, puede calcularse de una manera puramente formal, pero para esto necesitamos una "calculadora" adecuada. Pero si no es así, puede usar un algoritmo intuitivo bastante simple. Dentro de su marco, se registra uno u otro estado componente de la red, y luego, clasificando a través de todas las posibles situaciones de entrada, los estados componentes objetivo se determinan mediante "manejadores". Entonces, habiendo fijado el estado “000” correspondiente a los estados actuales del autómata componente: “a0”, “b0”, “c0”, se determinan las transiciones para las conjunciones de las variables de entrada ^ x1 ^ x2, ^ x1x2, x1 ^ x2, x1x2. Obtenemos las transiciones respectivamente en indica "a0b0c0", "a0b1c0", "a1b0c0", "a1b1c0", que están marcados como "000", "010", "100" y "110" en la máquina resultante. Debe repetir esta operación secuencialmente para todos los estados accesibles. buclesque no están cargados de acciones pueden excluirse del gráfico.

Lo que tenemos "en el residuo seco". Logramos lo principal: recibimos el autómata resultante, que describe con precisión el funcionamiento de la red. Descubrimos que de los ocho posibles estados de red, uno es inaccesible (aislado) - estado "001". Esto significa que la operación de suma no se activará bajo ninguna circunstancia para variables de entrada que no hayan cambiado el valor actual.

Lo cual es inquietante, aunque las pruebas no revelaron errores. En el gráfico del autómata resultante, se encontraron transiciones conflictivas en las acciones de salida. Están marcados con una combinación de las acciones y1y3 e y2y3. Las acciones y1 e y2 se activan cuando los datos de entrada cambian, y luego otra acción y3 calcula la suma de las variables en paralelo con ellas. ¿En qué valores operará, antiguos o simplemente cambiados por otros nuevos? Para eliminar la ambigüedad, simplemente puede cambiar las acciones de y3 e y4. En este caso, su código será el siguiente: X3 = X1Sav + X2Sav e print (X1Sav, X2Sav, X3).

Entonces. La construcción del autómata resultante reveló problemas obvios en el modelo paralelo creado. Si aparecen en el programa reactivo es una pregunta. Aparentemente, todo dependerá del enfoque para la implementación del paralelismo en el paradigma reactivo. En cualquier caso, dicha dependencia debe tenerse en cuenta y eliminarse de alguna manera. En el caso de una red automatizada, es más fácil dejar la versión modificada que intentar cambiar la red. Está bien si los datos "antiguos" que iniciaron la operación de la red se imprimen primero, y luego los datos actuales se imprimen a continuación.

4. Conclusiones


Cada una de las soluciones consideradas tiene sus ventajas y desventajas. La inicial es muy simple, la red es más complicada y, creada sobre la base de una sola máquina, comenzará a analizar los datos de entrada solo después de su visualización. Debido a su paralelismo, la misma red automática comenzará el análisis de los datos de entrada antes del final del procedimiento de impresión. Y si el tiempo de visualización es largo, pero este será el caso contra la operación de suma, entonces la red será más rápida desde el punto de vista del control de entrada. Aquellos. Una evaluación basada en una estimación de la cantidad de código en el caso de programas paralelos no siempre es objetiva. En términos más simples, la red es paralela, la solución de un componente es en gran medida secuencial (sus predicados y acciones son paralelas). Y nosotros, antes que nada, estamos hablando de programas paralelos.

El modelo de red también es un ejemplo de una solución flexible. Primero, los componentes pueden diseñarse independientemente uno del otro. En segundo lugar, cualquier componente puede ser reemplazado por otro. Y en tercer lugar, cualquier componente de red puede ser un elemento de una biblioteca de procesos automáticos y se utiliza en otra solución de red. Y estos son solo los beneficios más obvios de una solución paralela.

Pero volvamos a la programación reactiva. ¿RP considera que todas las declaraciones del programa son inicialmente paralelas? Solo podemos suponer que sin esto es difícil hablar de un paradigma de programación "orientado hacia los flujos de datos y la propagación de cambios" (véase la definición de programación reactiva en [3]). Pero, ¿cuál es su diferencia con la programación con control de transmisión (para más detalles ver [1])? Entonces volvemos a donde comenzamos: ¿cómo clasificar la programación reactiva en el marco de clasificaciones bien conocidas? Y, si RP es una programación especial, entonces, ¿en qué se diferencia de los paradigmas de programación conocidos?

Bueno, sobre la teoría. Sin él, el análisis de algoritmos paralelos no solo sería difícil, imposible. El proceso de análisis a veces revela problemas que, incluso con una mirada cuidadosa y reflexiva al programa, como, por cierto, al "documento de diseño", es imposible de adivinar. En cualquier caso, estoy a favor del hecho de que los aviones, tanto en sentido figurado como en cualquier otro sentido, no se estrellan. Soy yo ante el hecho de que, por supuesto, debes esforzarte por la simplicidad y la gracia de la forma, pero sin pérdida de calidad. Nosotros, los programadores, no solo "dibujamos" programas, sino que a menudo controlamos lo que está oculto allí, ¡incluso en los aviones!

Sí, casi lo olvido. Clasificaría la programación automática (AP) como programación con control dinámico. En cuanto a la asincronía, apuesto. Dado que la base del modelo de control AP es una red en un solo tiempo, es decir redes síncronas de autómatas, entonces es síncrono. Pero dado que el entorno VKPa también implementa muchas redes a través del concepto de "mundos de autómatas", es completamente asíncrono. En general, estoy en contra de cualquier marco de clasificación muy rígido, pero no a favor de la anarquía. En este sentido, en VKPa, espero que se haya alcanzado un cierto compromiso entre la rigidez de la programación en serie y en paralelo y cierto anarquismo asincrónico. Dado el hecho de que la programación automática también cubre la clase de programas de eventos (ver [4]), y los programas de transmisión se modelan fácilmente dentro de ella,¿Con qué programación puedes seguir soñando? Por supuesto, para mí.

Literatura
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