Sobre corutinismo competitivo (usando programação reativa como exemplo)

1. Introdução


A competição pelas mentes, humores e aspirações dos programadores é, como me parece, uma tendência moderna no desenvolvimento da programação. Quando quase nada é proposto, embora sob o lema da luta por ele. É muito, muito difícil reconhecer, no meio dos paradigmas de software, algo novo, que na verdade costuma ser bem conhecido e, às vezes, simplesmente desatualizado. Tudo é "lavado" por delícias terminológicas, análises detalhadas e exemplos de múltiplas linhas em muitas linguagens de programação. Ao mesmo tempo, solicitações para abrir e / ou considerar o plano de fundo da solução, a essência das inovações é obstinadamente evitada, as tentativas de descobrir quanto isso é necessário e o que dará no final, o que distingue qualitativamente a inovação das abordagens e ferramentas de programação já conhecidas, são frustrados pela raiz.

Eu apareci em Habré, como foi notado em uma das discussões, depois de um certo congelamento. Eu nem me importo. Pelo menos, a impressão, aparentemente, é exatamente isso. Portanto, concordo, confesso, embora, se a culpa for minha, seja apenas parcialmente. Admito que vivo de acordo com as idéias sobre programação paralela, formadas nos anos 80 do século passado. Antiguidade? Talvez. Mas me diga o que há de novo, sobre o qual a ciência da programação [paralela] ainda não seria conhecida (veja detalhes [1]). Naquela época, os programas paralelos eram divididos em duas classes - paralelo-serial e assíncrono. Se o primeiro já era considerado arcaico, o segundo - avançado e verdadeiramente paralelo. Entre os últimos, destacou-se a programação com controle de eventos (ou apenas programação de eventos), controle de fluxo e programação dinâmica.Isso é tudo em geral. Mais detalhes já.

E o que a programação atual oferece além do que já é conhecido há pelo menos 40 anos? No meu "olhar congelado" - nada. As corotinas, como se viu, agora são chamadas de corotinas ou até goroutinas; os termos concorrência e concorrência entram em um estupor, ao que parece, não apenas tradutores. E não existem exemplos. Por exemplo, qual é a diferença entre programação reativa (RP) e programação ou streaming de eventos? Em quais das categorias e / ou classificações conhecidas se enquadra? Ninguém parece estar interessado nisso, e ninguém pode esclarecer isso. Ou você pode classificar agora pelo nome? Então, de fato, corotinas e corotinas são coisas diferentes, e a programação paralela é simplesmente obrigada a diferir da competitiva. E as máquinas de estado? Que tipo de técnica milagrosa é essa?

O "espaguete" na cabeça surge do esquecimento da teoria em que, quando um novo modelo é introduzido, ele é comparado com modelos já conhecidos e bem estudados. Se isso será feito bem, mas pelo menos você pode descobrir, porque o processo é formalizado. Mas como chegar ao fundo da questão se você der um novo apelido às corotinas e depois escolher o “código de capa” simultaneamente em cinco idiomas, avaliando ainda a perspectiva de migração para threads. E essas são apenas corotinas, que, francamente, já devem ser esquecidas por causa de sua natureza elementar e seu pequeno uso (é, é claro, sobre a minha experiência).

2. Programação reativa e tudo, tudo, tudo


Não estabeleceremos o objetivo de entender completamente o conceito de “programação reativa”, embora tomemos o “exemplo reativo” como base para uma discussão mais aprofundada. Seu modelo formal será criado com base no conhecido modelo formal. E isso, espero, nos permitirá entender de maneira clara, precisa e detalhada a interpretação e operação do programa original. Mas quanto o modelo criado e sua implementação serão "reativos" cabe aos apologistas desse tipo de programação decidir. No momento, basta que o novo modelo tenha que implementar / modelar todas as nuances do exemplo original. Se algo não for levado em consideração, espero que haja quem me corrija.

Portanto, em [2], foi considerado um exemplo de programa reativo, cujo código é mostrado na Listagem 1.

Listagem 1. Código do programa reativo
1. 1 = 2 
2. 2 = 3 
3. 3 = 1 + 2 
4.  1, 2, 3 
5. 1 = 4 
6.  1, 2, 3


No mundo da programação reativa, o resultado de seu trabalho será diferente do resultado de um programa regular do mesmo tipo. Isso por si só é ruim, se não para dizer feiura, porque O resultado do programa deve ser inequívoco e não depender da implementação. Mas mais confunde o outro. Em primeiro lugar, na aparência, dificilmente é possível distinguir um código semelhante regular de um código reativo. Em segundo lugar, aparentemente, o próprio autor não está inteiramente certo do trabalho do programa reativo, falando sobre o resultado "mais provável". E terceiro, qual dos resultados é considerado correto?

Tal ambiguidade na interpretação do código levou ao fato de que não é imediatamente possível "cortá-lo". Mas então, como costuma acontecer, tudo se tornou muito mais simples do que se poderia esperar. A Figura 1 mostra dois diagramas estruturais que, esperançosamente, correspondem à estrutura e explicam a operação do exemplo. No diagrama superior, os blocos X1 e X2 organizam a entrada de dados, sinalizando o bloco X3 sobre suas alterações. O último executa a soma e permite que o bloco Pr imprima os valores atuais das variáveis. Depois de impresso, o bloco Pr sinaliza para o bloco X3, além disso, para ele e somente para ele que ele está pronto para imprimir novos valores.

FIG. 1. Dois modelos estruturais do exemplo
image

O segundo esquema, em comparação com o primeiro, é bastante elementar. Como parte de um único bloco, ele insere dados e implementa sequencialmente: 1) calculando a soma dos dados de entrada e 2) imprimindo-os. O preenchimento interno do bloco neste nível de apresentação não é divulgado. Embora se possa dizer que, no nível estrutural, pode ser uma "caixa preta", incluindo um esquema de quatro blocos. Mas, ainda assim, seu dispositivo [algorítmico] deve ser diferente.

Comente. A abordagem do programa como uma caixa preta reflete essencialmente a atitude do usuário em relação a ele. Este último não está interessado em sua implementação, mas no resultado do trabalho. Seja um programa reativo, um programa de eventos ou algum outro, mas o resultado de acordo com a teoria dos algoritmos deve ser inequívoco e previsível.

Na fig. 2 apresenta modelos algorítmicos que esclarecem em detalhes a estrutura [algorítmica] interna dos blocos de circuito. O modelo superior é representado por uma rede de autômatos, onde cada um dos autômatos é um modelo algorítmico de um bloco separado. As conexões entre os autômatos mostrados pelos arcos tracejados correspondem às conexões do circuito. Um modelo de autômato único descreve o algoritmo de operação de um diagrama de blocos que consiste em um bloco (consulte um bloco Pr separado na Fig. 1).

FIG. 2. Modelos algorítmicos para esquemas estruturais
image

Os autômatos X1 e X2 (os nomes dos autômatos e blocos coincidem com os nomes de suas variáveis), detectam as alterações e, se o autômato X3 estiver pronto para executar a operação de adição (no estado "s0"), entra no estado "s1", lembrando o valor atual da variável. A máquina X3, tendo recebido permissão para entrar no estado “s1”, executa a operação de adição e, se necessário, aguarda a conclusão da impressão das variáveis. “A máquina de impressão“ Pr, após concluir a impressão, retorna ao estado inicial “p0”, onde aguarda o próximo comando. Observe que seu estado "p1" inicia uma cadeia de transições reversas - o autômato X3 para o estado "s0" e X1 e X2 para o estado "s0". Depois disso, a análise dos dados de entrada e o somatório e a impressão subsequente são repetidos.

Comparado à rede de autômatos, o algoritmo de um autômato Pr separado é bastante simples, mas, observamos, ele faz o mesmo trabalho e talvez até mais rápido. Seus predicados revelam uma mudança nas variáveis. Se isso acontecer, a transição para o estado “p1” é realizada com o início da ação y1 (veja a Fig. 2), que resume os valores atuais das variáveis, lembrando-os. Em uma transição incondicional do estado “p1” para o estado “p0”, a ação y2 imprime as variáveis. Depois disso, o processo retorna à análise dos dados de entrada. O código de implementação para o modelo mais recente é mostrado na Listagem 2.

Listagem 2. Implementação do autômato 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);
}


A quantidade de código é claramente incomparavelmente maior que o exemplo original. Mas, observe, nem um único código. A nova solução remove todos os problemas de funcionamento, não permitindo fantasias na interpretação do programa. Um exemplo que parece compacto e elegante, mas sobre o qual você pode dizer "provavelmente", não causa, digamos, emoções positivas e um desejo de trabalhar com ele. Deve-se notar também que é necessário comparar realmente com a ação do autômato y1.

O restante do código está relacionado aos requisitos do "ambiente automático", que, observo, não é falado no código-fonte. Portanto, o método FCreationOfLinksForVariables da classe de autômato base LFsaApplcria variáveis ​​locais para a máquina e vincula a elas quando no nível do ambiente VKPA são indicados nomes simbólicos das outras variáveis ​​de ambiente associadas a elas. A primeira vez que ele inicia ao criar um autômato e depois dentro da estrutura do método FInit (consulte a etapa y12), porque nem todos os links são conhecidos ao criar um objeto. A máquina permanecerá no estado "st" até que todos os links necessários que as verificações do predicado x12 sejam inicializados. Uma referência a uma variável, se receber seu nome, retorna o método GetAddressVar.

Para remover possíveis perguntas, apresentamos o código da rede de autômatos. É mostrado na Listagem 3 e inclui o código para três classes de autômatos. É com base nisso que são criados muitos objetos que correspondem ao diagrama estrutural da rede mostrado na Fig. 1. Observe que os objetos X1 e X2 são derivados da classe geral FSynch.

Listagem 3. Classes de rede 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);
}


Esse código é diferente da Listagem 1, como a figura de um avião de sua documentação de design. Penso, porém, que somos principalmente programadores e, sem ofensas, alguns designers. Nosso "código de projeto" deve ser fácil de entender e interpretar de forma inequívoca, para que nosso "avião" não caia no primeiro vôo. E se tal infortúnio aconteceu, e com programas isso acontece com mais frequência do que com aviões, então o motivo pode ser encontrado com facilidade e rapidez.

Portanto, considerando a Listagem 3, você precisa imaginar que o número de classes não está diretamente relacionado ao número de objetos correspondentes no programa paralelo. O código não reflete o relacionamento entre objetos, mas contém os mecanismos que os criam. Portanto, a classe FSynch contém um ponteiro pL para um objeto do tipoLFsaAppl . O nome desse objeto é determinado por uma variável local, que no ambiente VKPa corresponderá a uma variável de autômato com o nome strNameObject . Um ponteiro é necessário para usar o método FGetState para monitorar o estado atual de um objeto de autômato do tipo FSynch (consulte o código de predicado x2). Ponteiros semelhantes para objetos, variáveis ​​para especificar os nomes de objetos e predicados necessários para organizar relacionamentos contêm outras classes.

Agora, algumas palavras sobre a "construção" de um programa paralelo no ambiente VKPA. É criado durante o carregamento da configuração do programa. Nesse caso, os primeiros objetos são criados com base em classes de bibliotecas dinâmicas temáticas de um tipo de autômato (seu conjunto é determinado pela configuração do aplicativo / programa). Objetos criados são identificados por seus nomes (vamos chamá-los de variáveis ​​automáticas) Em seguida, os valores necessários são gravados nas variáveis ​​locais dos autômatos. No nosso caso, variáveis ​​com um tipo de sequência são definidas para os nomes de variáveis ​​de outros objetos e / ou os nomes dos objetos. Dessa maneira, as conexões entre os objetos de um programa de autômato paralelo são estabelecidas (veja a Fig. 1). Além disso, alterando os valores das variáveis ​​de entrada (usando os diálogos individuais de controle de objeto ou os diálogos padrão de diálogo / ambiente para definir valores para variáveis ​​de ambiente), corrigimos o resultado. Pode ser visto usando um diálogo de ambiente padrão para exibir os valores das variáveis.

3. À análise de programas paralelos


No funcionamento de um programa paralelo, a menos que seja bastante simples sequencialmente paralelo, é muito, muito difícil dizer algo concreto. A rede de autômatos considerada não é exceção. A seguir, veremos isso, entendendo o que se pode esperar dele.

O autômato resultante e a rede para a qual ele é construído são mostrados na Fig. 3. A partir da rede na Fig. 2, além de renomear seus elementos - autômatos, sinais de entrada e saída, distingue-se pela ausência de uma “máquina de impressão” de variáveis. O último não é essencial para a operação da rede e a renomeação permite que você use a operação de composição para criar o autômato resultante. Além disso, para criar nomes mais curtos, a codificação foi introduzida quando, por exemplo, o estado "a0" do autômato A é representado pelo símbolo "0" e "a1" pelo símbolo "1". Da mesma forma para outras máquinas. Nesse caso, o estado do componente da rede, por exemplo, "a1b0c1", recebe o nome "101". Da mesma forma, os nomes são formados para todos os estados componentes da rede, cujo número é determinado pelo produto dos estados dos autômatos dos componentes.

FIG. 3. O autômato de rede resultante
image

O autômato resultante pode, é claro, ser calculado de maneira puramente formal, mas para isso precisamos de uma “calculadora” apropriada. Mas se não for, você pode usar um algoritmo intuitivo bastante simples. Dentro de sua estrutura, um ou outro estado de componente da rede é gravado e, em seguida, classificando todas as possíveis situações de entrada, os estados de componente de destino são determinados por "identificadores". Assim, tendo fixado o estado "000" correspondente aos estados atuais dos autômatos do componente - "a0", "b0", "c0", transições para as conjunções das variáveis ​​de entrada ^ x1 ^ x2, ^ x1x2, x1 ^ x2, x1x2 são determinadas. Obtemos as transições respectivamente em indica "a0b0c0", "a0b1c0", "a1b0c0", "a1b1c0", marcados como "000", "010", "100" e "110" na máquina resultante. Você deve repetir esta operação sequencialmente para todos os estados alcançáveis. rotaçõesque não são carregados com ações podem ser excluídos do gráfico.

O que temos "no resíduo seco". Conseguimos o principal - recebemos o autômato resultante, que descreve com precisão o funcionamento da rede. Descobrimos que dos oito possíveis estados da rede, um é inacessível (isolado) - estado “001”. Isso significa que a operação de somatória nunca será acionada para variáveis ​​de entrada que não alteraram o valor atual.

O que é preocupante, embora o teste não tenha revelado erros. No gráfico do autômato resultante, foram encontradas transições conflitantes nas ações de saída. Eles são marcados com uma combinação das ações y1y3 e y2y3. As ações y1 e y2 são acionadas quando os dados de entrada são alterados e, em seguida, outra ação y3 calcula a soma das variáveis ​​em paralelo com elas. Em quais valores ele operará - antigo ou apenas alterado por novos? Para eliminar a ambiguidade, você pode simplesmente alterar as ações de y3 e y4. Nesse caso, o código será o seguinte: X3 = X1Sav + X2Sav e print (X1Sav, X2Sav, X3).

Assim. A construção do autômato resultante revelou problemas óbvios no modelo paralelo criado. Se eles aparecem no programa reativo é uma questão. Aparentemente, tudo vai depender da abordagem da implementação do paralelismo no paradigma reativo. De qualquer forma, essa dependência deve ser levada em consideração e de alguma forma eliminada. No caso de uma rede automatizada, é mais fácil sair da versão alterada do que tentar mudar a rede. Tudo bem se os dados "antigos" que iniciaram a operação da rede forem impressos primeiro e depois os dados atuais forem impressos a seguir.

4. Conclusões


Cada uma das soluções consideradas tem seus prós e contras. A inicial é muito simples, a rede é mais complicada e, criada com base em uma única máquina, começará a analisar os dados de entrada somente após a visualização. Devido ao seu paralelismo, a mesma rede automática iniciará a análise dos dados de entrada antes do final do procedimento de impressão. E se o tempo de visualização for longo, mas esse for o caso da operação de soma, a rede será mais rápida do ponto de vista do controle de entrada. Essa. uma avaliação baseada em uma estimativa da quantidade de código no caso de programas paralelos nem sempre é objetiva. Em termos mais simples, a rede é paralela, a solução de um componente é amplamente seqüencial (seus predicados e ações são paralelas). E nós, antes de tudo, estamos falando sobre programas paralelos.

O modelo de rede também é um exemplo de solução flexível. Primeiro, os componentes podem ser projetados independentemente um do outro. Em segundo lugar, qualquer componente pode ser substituído por outro. E terceiro, qualquer componente de rede pode ser um elemento de uma biblioteca de processos automáticos e é usado em outra solução de rede. E esses são apenas os benefícios mais óbvios de uma solução paralela.

Mas voltando à programação reativa. O RP considera todas as instruções do programa inicialmente paralelas? Só podemos assumir que sem isso é difícil falar sobre um paradigma de programação “orientado para fluxos de dados e propagação de mudanças” (veja a definição de programação reativa em [3]). Mas qual é a diferença da programação com controle de streaming (para mais detalhes, veja [1])? Então, voltamos ao ponto em que começamos: como classificar a programação reativa no quadro de classificações conhecidas? E, se RP é algo de programação especial, então o que é diferente dos paradigmas de programação conhecidos?

Bem, sobre a teoria. Sem ele, a análise de algoritmos paralelos não seria apenas difícil - impossível. O processo de análise às vezes revela problemas que, mesmo com uma análise cuidadosa e cuidadosa do programa, como, aliás, no "documento de design", é impossível adivinhar. De qualquer forma, sou a favor do fato de que os aviões, tanto em sentido figurado quanto em qualquer outro sentido, não colidem. Sou eu o fato de que, é claro, você precisa se esforçar pela simplicidade e graça da forma, mas sem perda de qualidade. Nós, programadores, não apenas “desenhamos” programas, mas frequentemente controlamos o que está oculto ali, inclusive por aviões!

Sim, quase esqueci. Eu classificaria a programação automática (AP) como programação com controle dinâmico. Quanto à assincronia - eu aposto. Dado que a base do modelo de controle do AP é uma rede em um único momento, ou seja, redes síncronas de autômatos, então é síncrono. Mas como o ambiente VKPa também implementa muitas redes através do conceito de "mundos de autômatos", é completamente assíncrono. Em geral, sou contra qualquer estrutura de classificação muito rígida, mas não a favor da anarquia. Nesse sentido, em VKPa, espero que um certo compromisso tenha sido alcançado entre a rigidez da programação paralela serial e um certo anarquismo assíncrono. Dado o fato de que a programação automática também abrange a classe de programas de eventos (consulte [4]), e os programas de fluxo são facilmente modelados nela,com qual programação você ainda pode sonhar? Com certeza - para mim.

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