Modelo de computação paralela

1. Introdução. Corutinismo competitivo


Os artigos anteriores sobre o tema da programação automática eram apenas "flores". A "baga" da programação automática, isto é, para o que você precisa fazer, é um modelo de computação paralela baseado no modelo de máquina de estado. Então, vamos lá ...

O padrão C ++ incluía o tão esperado suporte para multithreading [1]. Mas não vamos admirar nem criticar esse fato, porque o trabalho com threads é oprimido por tantas condições, advertências e recursos que, sem exemplos reais que revelam problemas de multithreading, uma discussão sobre programação multithread não será apenas apressada, mas também bastante tendenciosa. Portanto, a seguir, principalmente, não sobre fluxos, mas sobre autômatos, tendo em vista, é claro, os primeiros.

A linguagem C ++ está longe de ser a primeira, complementada por construções de paralelismo. Nos anos 60 do século passado, N. Wirth propôs uma extensão paralela da linguagem ALGOL [2]. No entanto, os próximos 60 anos não esclareceram o que deveria ser considerado um algoritmo paralelo e qual deveria ser o modelo da computação paralela. Aparentemente, uma extensão tão tardia da linguagem C ++ também está conectada a isso.

Tanto as construções de longa data do ALGOL quanto suas contrapartes mais modernas em C ++ representam apenas métodos de paralelização estrutural que não introduzem um modelo algorítmico paralelo. Para justificar isso, pode-se dizer que as tentativas feitas no passado para criar um modelo formal de cálculo falharam. Basta dizer que as mesmas redes de Petri não justificaram as grandes esperanças depositadas nelas.

Como resultado, a “espiral” do desenvolvimento do paralelismo parece ter retornado à sua fonte, tendo passado apenas pelo “desenvolvimento terminológico”. As antigas corotinas triviais tornaram-se repentinamente avançadas "corotinas" (papel vegetal da corotina inglesa), e a confusão com os conceitos de paralelo e simultâneo no segmento de programação paralela em inglês às vezes leva a coisas paradoxais. Por exemplo, a primeira edição do livro [1] difere da segunda edição, substituindo os termos "paralelo" por "competitivo" e "multithread" por "paralelo". Então, descubra isso na situação "quem é quem".

2. Modelo paralelo de autômatos de cálculos


Provavelmente ninguém contestará que o próximo passo qualitativo no desenvolvimento da programação esteja relacionado à transição para um modelo de computação paralela. Mas se isso acontecerá como resultado do desenvolvimento evolutivo do modelo computacional existente ou se será um modelo fundamentalmente diferente é a questão ainda em discussão. E se os teóricos ainda estão discutindo, a parte praticamente motivada dos programadores já está usando métodos estruturais para paralelizar programas.

A separação de tarefas e o aumento da produtividade são considerados os únicos motivos para o uso da simultaneidade. Pelo menos para eles ou suas combinações, em última análise, reduzem ou tentam reduzir todos os outros [1]. Mas há uma razão que raramente se fala, mas por causa da qual geralmente vale a pena se engajar em programação paralela. De fato, a velocidade pode ser aumentada por métodos puramente de hardware, e a separação de tarefas com paralelismo é conectada da mesma maneira que o trabalho diário dos funcionários do banco com uma lista de suas funções oficiais. E apenas algoritmos paralelos são uma estratégia que nos permite derrotar a complexidade das tarefas e aumentar a confiabilidade dos programas. E tudo isso é contrário à opinião predominante sobre a programação multithread, que transforma qualquer programa paralelo em um produto de software complexo e não confiável.

Um sistema paralelo, consistindo em muitos componentes, objetos, agentes, etc. que funcionam ativamente em paralelo, implementa um algoritmo que é determinado de várias maneiras, não pelos algoritmos dos componentes individuais (embora, é claro), mas pelo número de componentes, número e número de componentes. tipo de conexões entre eles. Para controlar essa complexidade e entender o algoritmo do sistema paralelo, você precisa não apenas de um modelo de computação paralela, mas de um modelo que tenha, entre outras coisas, e acima de tudo, uma teoria apropriada.

A tese de que "geralmente um programa paralelo é mais difícil de entender ... e, portanto, o número de erros está aumentando", para dizer o mínimo, é discutível. Sim, o algoritmo de programa paralelo pode ser bastante difícil de entender, mas se houver uma teoria, ela pode ser "calculada" formalmente usando algoritmos de componentes. E do ponto de vista de projetar, implementar e manter os algoritmos de componentes são muito mais simples que o algoritmo do sistema como um todo. Ao projetar componentes mais simples, obviamente cometeremos menos erros do que projetar um sistema inteiro. Além disso, os componentes depurados podem fazer parte de outros sistemas, reduzindo a complexidade, aumentando a confiabilidade e minimizando os custos de design.

3. Concorrência serial


O artigo [3] descreveu o paralelismo de um modelo separado de uma máquina de estados finitos. Seus canais no nível de execução de transições especificam a execução paralela das funções / métodos associados a ela - predicados e ações. Ao mesmo tempo, não há restrições no paralelismo de predicado. Ao trabalhar, eles não entram em conflito, porque não afeta o conteúdo da memória. As ações, trabalhando em paralelo, podem ter dados comuns de entrada e saída, além de alterá-los independentemente um do outro. E tudo isso pode ser uma fonte de incerteza no valor dos dados de saída.

A operação correta das ações nas situações descritas acima fornece memória de sombra. Ao armazenar novos valores, é possível usar os mesmos dados em uma única ação, tanto como entrada quanto como saída. Um exemplo é o modelo de um gerador de pulsos retangular, descrito como y =! Y, onde y é a saída do gerador. Seu código C ++ no ambiente VKPa é mostrado na Listagem 1 e os resultados do programa são mostrados na Fig. 1

Lista 1. Gerador de pulso retangular
#include "lfsaappl.h"

class FSWGenerator :
    public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FSWGenerator(pTAppCore, nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();
    FSWGenerator(TAppCore *pInfo, string strNam, CVarFsaLibrary *pCVFL);
    virtual ~FSWGenerator(void) {};
    CVar *pVarY;				//  
protected:
    void y1();
};

#include "stdafx.h"
#include "FSWGenerator.h"
// state machine transition table
static LArc TBL_SWGenerator[] = {
    LArc("s1",		"s1","--",	"y1"),			//
    LArc()
};

FSWGenerator::FSWGenerator(TAppCore *pInfo, string strNam, CVarFsaLibrary *pCVFL):
    LFsaAppl(TBL_SWGenerator, strNam, nullptr, pCVFL)
{
    pTAppCore = pInfo;
}
// creating local variables and initialization of pointers
bool FSWGenerator::FCreationOfLinksForVariables() {
// creating local variables
    pVarY = CreateLocVar("y", CLocVar::vtBool, " ");
    return true;
}
// setting output signals
void FSWGenerator::y1() {
    pVarY->SetDataSrc(nullptr, !bool(pVarY->GetDataSrc()));
}


imagem
FIG. 1. Simulação da operação do gerador de pulso retangular no VKPA

Nesse caso, a máquina possui um estado com uma transição incondicional (uma transição com um hífen no lugar da condição de entrada) na forma de um loop marcado pela ação y1, que implementa a inversão da variável de saída, que forma um pulso retangular na dinâmica. Dentro da estrutura do modelo de autômato, a frequência do sinal de pulso pode ser controlada definindo o valor de tato do tempo discreto do espaço do autômato no qual o autômato é carregado.

1. , . . . .


A capacidade de controlar o tempo discreto do autômato e a presença de muitos espaços de autômato não são as únicas, mas importantes, propriedades distintas do ambiente VKPa. Usando-os, você pode otimizar o desempenho de um programa paralelo. Por exemplo, máquinas que implementam visualização de dados e caixas de diálogo do usuário devem ser colocadas em espaços lentos de autômatos, e os processos de aplicativos devem ser distribuídos entre os espaços de autômatos, de acordo com as prioridades e a velocidade desejada, etc. etc.

Dentro da estrutura de um modelo de autômato, o valor da saída do gerador é facilmente relacionado ao estado atual do modelo. O código para o modelo do gerador, que já possui dois estados, cada um dos quais reflete o estado da saída do gerador, é mostrado na Listagem 2.

Listagem 2. Gerador de ondas quadradas em estados
#include "lfsaappl.h"

extern LArc TBL_SWGenState[];
class FSWGenState :
    public LFsaAppl
{
public:
    FSWGenState(TAppCore *pInfo, string strNam, CVarFsaLibrary *pCVFL):
    LFsaAppl(TBL_SWGenState, strNam, nullptr, pCVFL) {};
};

#include "stdafx.h"
#include "FSWGenState.h"
// state machine transition table
LArc TBL_SWGenState[] = {
    LArc("s0",		"s1","--",	"--"),			//
    LArc("s1",		"s0","--",	"--"),			//
    LArc()
};


No novo modelo, os estados substituem a variável de saída, e isso, como pode ser visto, simplificou dramaticamente o modelo do gerador. Como resultado, obtivemos uma máquina "vazia", ​​representada apenas por uma tabela de conversão. Para monitorar seu estado atual “s1” em VKPa, uma variável do tipo fsa (state) com o nome SWGenState. (S1) foi criada para a máquina com o nome SWGenState. É preciso um valor verdadeiro no estado s1 e falso quando a máquina está em um estado diferente. Além disso, essa variável já é usada por meio da exibição dos dados do ambiente VKPA (veja a tendência do sinal na Fig. 2).

imagem
FIG. 2. Modelando o Gerador de Estado

4. Modelo de controle de computação paralela


Além disso, caminhando para a criação de um modelo de processos paralelos, é lógico usar muitas máquinas de estados finitos que funcionam e interagem simultaneamente, ou seja, rede de autômatos. Nesse caso, aparece o problema de escolher um modelo de horário da rede, que pode ser o mesmo para todas as máquinas ou, no limite, individual para cada uma. No VKPa, a escolha foi feita em favor de uma única vez (para obter mais detalhes sobre redes síncronas de autômatos, consulte [5]).

A escolha de uma única vez permite criar uma álgebra de autômatos com operações de composição e decomposição de autômatos. Usando o primeiro, é possível encontrar o autômato resultante, que fornece uma idéia precisa da operação de um sistema paralelo. E aqui vale a pena recordar a tese acima sobre a "complexidade da compreensão" de programas paralelos. A presença da operação de composição nos permite resolver o "problema de entendimento" do programa paralelo.

Obviamente, o autômato resultante para uma rede de um grande número de componentes pode ser muito volumoso. Felizmente, porém, é mais necessário o entendimento da operação de subsistemas ou redes de um pequeno número de componentes, para os quais a localização do autômato resultante não causa grandes problemas. O seguinte exemplo de modelo de flip-flop RS demonstra isso.

O modelo de gatilho RS é um exemplo de um sistema paralelo simples. É especialmente interessante na presença de feedbacks cruzados. Feedbacks, ou, de outra maneira, cadeias cíclicas, loops, loops algébricos, etc. atualmente são um problema sério para modelos estruturais de sistemas paralelos. No caso geral, é permitido introduzir nos loops de gap dos elementos de memória. Esta é a solução padrão proposta pela teoria dos autômatos [4]. A mesma saída é recomendada na pessoa do MATLAB. O ambiente do VKPa é diferente, pois não requer a introdução desses elementos adicionais para a implementação de loops. Observe, e isso é muito importante, os circuitos reais também não precisam deles (consulte o circuito RS-flip-flop).

Na fig. A Figura 3 apresenta o modelo mais simples do elemento AND-NOT, no qual o circuito de disparo RS consiste. Ele não leva em consideração os atrasos dos elementos, bem como seu tipo (transporte ou atrasos inerciais). No entanto, ele ainda contém pelo menos um atraso de atraso. Este é o momento da transição de um estado para outro. A Listagem 3 mostra o código do modelo

imagem
. 3. O modelo do elemento AND NOT

Listagem 3. Modelo de elemento AND NOT
#include "lfsaappl.h"

class FIne :
    public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FIne(pTAppCore, nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();
    FIne(TAppCore *pInfo, string strNam, CVarFsaLibrary *pCVFL);
    virtual ~FIne(void) {};
    CVar *pVarX1;				//  
    CVar *pVarX2;				//  
    CVar *pVarY;				//  
    CVar *pVarStrNameX1;		//    
    CVar *pVarStrNameX2;		//    
    CVar *pVarStrNameY;         //   
protected:
    int x1(); int x2(); int x12();
    void y1(); void y2(); void y12();
    bool bX1, bX2, bY;
};


#include "stdafx.h"
#include "FIne.h"
// state machine transition table
static LArc TBL_Ine[] = {
    LArc("st",		"st","^x12","y12"), 		//
    LArc("st",		"s1","x12^x1",	"y1"),		//
    LArc("st",		"s1","x12^x2",	"y1"),		//
    LArc("st",		"s0","x12x1x2",	"y2"),		//
    LArc("s1",		"s0","x1x2",   "y2"),		//
    LArc("s0",		"s1","^x1",    "y1"),		//
    LArc("s0",		"s1","^x2",    "y1"),		//
    LArc()
};

FIne::FIne(TAppCore *pInfo, string strNam, CVarFsaLibrary *pCVFL):
    LFsaAppl(TBL_Ine, strNam, nullptr, pCVFL)
{ }

// creating local variables and initialization of pointers
bool FIne::FCreationOfLinksForVariables() {
// creating local variables
    pVarX1 = CreateLocVar("x1", CLocVar::vtBool, " 1- ");
    pVarX2 = CreateLocVar("x2", CLocVar::vtBool, " 2- ");
    pVarY = CreateLocVar("y", CLocVar::vtBool, " ");
    pVarStrNameX1 = CreateLocVar("strNameX1", CLocVar::vtString, "name of external input variable(x1)");			//   
    pVarStrNameX2 = CreateLocVar("strNameX2", CLocVar::vtString, "name of external input variable(x2)");			//   
    pVarStrNameY = CreateLocVar("strNameY", CLocVar::vtString, "name of external output variable(y)");		//   
// initialization of pointers
    string str;
    if (pVarStrNameX1) {
        str = pVarStrNameX1->strGetDataSrc();
        if (str != "") { pVarX1 = pTAppCore->GetAddressVar(str.c_str(), this);	}
    }
    if (pVarStrNameX2) {
        str = pVarStrNameX2->strGetDataSrc();
        if (str != "") { pVarX2 = pTAppCore->GetAddressVar(str.c_str(), this); }
    }
    if (pVarStrNameY) {
        str = pVarStrNameY->strGetDataSrc();
        if (str != ""){pVarY = pTAppCore->GetAddressVar(str.c_str(), this);}
    }
    return true;
}

int FIne::x1() { return bool(pVarX1->GetDataSrc()); }
int FIne::x2() { return bool(pVarX2->GetDataSrc()); }
int FIne::x12() { return pVarX1 != nullptr && pVarX2 && pVarY; }

void FIne::y1() { pVarY->SetDataSrc(nullptr, 1); }
void FIne::y2() { pVarY->SetDataSrc(nullptr, 0.0); }
void FIne::y12() { FInit(); }


Na fig. 4 mostra um diagrama de um flip-flop RS e seu modelo na forma de uma máquina de estados finitos. As setas no modelo indicam as conexões entre os autômatos da rede. Aqui, por um lado, os estados dos modelos refletem os estados das saídas do elemento e, por outro lado, também são usados ​​como sinais para organizar links de informações entre processos paralelos. É essa forma de modelo (com sincronização por estados) que facilita a localização do autômato resultante da rede. É mostrado na fig. 5 (para o procedimento para encontrar o autômato resultante, consulte [6] para mais detalhes).

Compare o algoritmo [resultante] do programa paralelo RS-flip-flop e o algoritmo de operação de um elemento AND-NOT separado. A diferença é impressionante. Nesse caso, os algoritmos de componentes são criados por "alças" e o algoritmo de sistema paralelo é criado implicitamente - pela "inteligência artificial" da rede. Esta é a diferença qualitativa entre programas paralelos e seqüenciais: mudando apenas as comunicações (pelo menos uma), obteremos um algoritmo de trabalho completamente diferente. E definitivamente não será mais um gatilho RS. E, a propósito, outro autômato resultante.

imagem
FIG. 4. Esquema do RS-FF e modelo de rede

imagem
Fig. 5. O modelo de rede da máquina resultante acionador RS

A análise do autômato resultante na Fig. 5 fornece o seguinte "entendimento" do programa paralelo (e o gatilho real, é claro). Em primeiro lugar, ao alternar de um estado para outro, o gatilho passa necessariamente pelo estado "proibido" das saídas (e o que os livros didáticos dizem sobre isso?). Em segundo lugar, se o gatilho for acionado em um único estado de saídas (no estado "s1w1") e, em seguida, duas unidades forem alimentadas nas entradas, ele entrará no modo de geração, ou seja, comutação cíclica entre os estados “s1w1” e “s0w0” e (e você já ouviu falar sobre geração de gatilhos?).

Uma transição através de um estado proibido também ocorre em um gatilho real, mas o modo de geração é impossível devido à diferença nos atrasos dos elementos reais. FIG. A Figura 6 mostra o modo de geração do modelo de gatilho acionador, que existe enquanto as unidades nas entradas estiverem armazenadas.

Observação 2. Uma descrição típica da operação do gatilho RS é dada na esmagadora maioria dos casos, na forma de uma tabela verdade. Mas, para fazer isso, entendendo que um gatilho é um esquema seqüencial, é, de fato, deliberadamente enganoso para quem estuda esse tópico. Bem, nenhum gatilho não pode ter "estados proibidos"! Mas, por alguma razão, apenas alguns decidem descobrir essa verdade e, principalmente, discutem o problema de sua geração (ver, por exemplo, [7]).


FIG. 7 mostra a troca de um modelo de gatilho entre seus estados estáveis. Aqui, um único estado de entradas de acionador retém o estado atual das saídas de acionador e, quando essa ou aquela entrada é definida como zero, alterna para o estado oposto. Ao mesmo tempo, quando o gatilho é acionado, suas saídas no momento iguais a uma medida discreta tomam ao mesmo tempo um único Estado (proibido por quem?).

imagem
FIG. 6. Modo de geração de disparo RS

imagem
Fig. 7. Alternando entre o gatilho RS e os estados

Considere outro modelo de gatilho RS, consistindo em um estado e uma ação, ou seja, semelhante ao modelo na Listagem 1. Seu código é mostrado na Listagem 4. Esse modelo, como o modelo do gerador, não possui predicados e os valores do sinal sem nenhuma transformação intermediária são inseridos na ação y1. Isso é bom ou ruim? Por um lado, parece que é bom, porque o código se tornou mais simples, mas por outro lado ... na verdade não. E vamos entender as razões para isso agora.

Listagem 4. Modelo de elemento NAND de uma ação
#include "lfsaappl.h"

class FTwoOperators :
    public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FTwoOperators(pTAppCore, nameFsa, pCVarFsaLibrary); }
    bool FCreationOfLinksForVariables();
    FTwoOperators(TAppCore *pInfo, string strNam, CVarFsaLibrary *pCVFL);
    virtual ~FTwoOperators(void) {};
    CVar *pVarX1;				//  
    CVar *pVarX2;				//  
    CVar *pVarY;				//  
    CVar *pVarStrNameX1;		//    
    CVar *pVarStrNameX2;		//    
    CVar *pVarStrNameY;         //   
protected:
    int x12();
    void y1(); void y12();
    bool bX1, bX2, bY;
};

#include "stdafx.h"
#include "FTwoOperators.h"
// state machine transition table
static LArc TBL_TwoOperators[] = {
    LArc("st",		"st","^x12","y12"), 		//
    LArc("st",		"s1","x12", "--"),		//
    LArc("s1",		"s1","--",  "y1"),		//
    LArc()
};

FTwoOperators::FTwoOperators(TAppCore *pInfo, string strNam, CVarFsaLibrary *pCVFL):
    LFsaAppl(TBL_TwoOperators, strNam, nullptr, pCVFL)
{ }
// creating local variables and initialization of pointers
bool FTwoOperators::FCreationOfLinksForVariables() {
// creating local variables
    pVarX1 = CreateLocVar("x1", CLocVar::vtBool, " 1- ");
    pVarX2 = CreateLocVar("x2", CLocVar::vtBool, " 2- ");
    pVarY = CreateLocVar("y", CLocVar::vtBool, " ");
    pVarStrNameX1 = CreateLocVar("strNameX1", CLocVar::vtString, "name of external input variable(x1)");			//   
    pVarStrNameX2 = CreateLocVar("strNameX2", CLocVar::vtString, "name of external input variable(x2)");			//   
    pVarStrNameY = CreateLocVar("strNameY", CLocVar::vtString, "name of external output variable(y)");		//   
// initialization of pointers
    string str;
    if (pVarStrNameX1) {
        str = pVarStrNameX1->strGetDataSrc();
        if (str != "") { pVarX1 = pTAppCore->GetAddressVar(str.c_str(), this);	}
    }
    if (pVarStrNameX2) {
        str = pVarStrNameX2->strGetDataSrc();
        if (str != "") { pVarX2 = pTAppCore->GetAddressVar(str.c_str(), this); }
    }
    if (pVarStrNameY) {
        str = pVarStrNameY->strGetDataSrc();
        if (str != "") { pVarY = pTAppCore->GetAddressVar(str.c_str(), this);	}
    }
    return true;
}

int FTwoOperators::x12() { return pVarX1 != nullptr && pVarX2 && pVarY; }

void FTwoOperators::y1() {
// reading input signals
    bX1 = bool(pVarX1->GetDataSrc());
    bX2 = bool(pVarX2->GetDataSrc());
// setting output signals
    bY = !(bX1&&bX2);
    pVarY->SetDataSrc(nullptr, bY);
}
// initialization of pointers
void FTwoOperators::y12() { FInit(); }


Se testarmos o novo modelo no modo "memória de sombra", não veremos diferenças em sua operação em relação ao anterior, ou seja, e, alternando, passará pelos estados proibidos e entrará regularmente no modo de geração. Se configurarmos o trabalho com dados no modo usual, obteremos os resultados mostrados na Fig. 8 e fig. 9.

imagem
Fig. 8. Falha no modo de geração do modelo de acionador RS

imagem
. 9. Ignorando estados proibidos pelo

modelo de acionador RS Por que o primeiro modelo, independentemente do modo de trabalhar com a memória, mostra resultados estáveis ​​e o segundo - altera o comportamento? A razão é predicados. O segundo modelo não possui predicados, e isso é crítico para seu comportamento. Mas como e por que a presença / ausência de predicados afeta o algoritmo de operação do programa paralelo?

O modelo de programa de um elemento AND-NOT, como um programa de autômato, possui dois canais de entrada e um canal de saída. Eles devem corresponder a dois predicados e uma ação. O primeiro programa é totalmente consistente com isso. O kernel do VKPa, que interpreta a descrição do autômato, primeiro executa todos os predicados não apenas de um único autômato, mas de todo o espaço do autômato, e só então inicia todas as ações. Nesse caso, em qualquer sequência em que as ações foram executadas, simulando o paralelismo e em qualquer modo em que funcionassem com a memória, os resultados dos predicados no ciclo de relógio atual do autômato não dependem deles (ações). Portanto, o primeiro programa produz o mesmo resultado.

O segundo programa, embora funcione diretamente com os canais de entrada da máquina, lê os sinais de entrada como parte da ação. As ações, trabalhando com dados de entrada no modo de memória de sombra, gravam novos valores na memória de sombra e, assim, trabalham com dados válidos no início de um ciclo de relógio discreto. No modo usual, eles “capturam” os valores instantâneos estabelecidos no momento da mudança e, portanto, o algoritmo se torna dependente dos momentos de mudança de memória. Uma dependência semelhante é demonstrada pelo segundo programa. E mesmo que métodos predicados fossem introduzidos no segundo modelo, isso não teria nenhum efeito nos resultados de seu trabalho. O que importa aqui não é o fato da existência de métodos predicados, mas os recursos de seu trabalho na estrutura do modelo de programação de autômatos.

5. Conclusões


Usando o programa paralelo de acionamento RS como exemplo, examinamos algumas das propriedades inerentes a qualquer programa paralelo. Continuaremos a considerar certos aspectos gerais do funcionamento de programas paralelos como um exemplo de circuitos lógicos (digitais). A escolha do tópico de modelagem de circuitos digitais aqui não é acidental. De fato, de forma refinada, representam o trabalho de processos paralelos. Isso faz a análise das nuances de simultaneidade, raça, sincronização, becos sem saída, etc. etc. transparente, claro e simples.

Ao mesmo tempo, não importa como você chame programação - “competitivo” ou paralelo, se você usa “corotinas”, corotinas, threads ou autômatos para programação, o resultado do programa [paralelo] deve ser o mesmo em todas as implementações. O modelo automático de programas paralelos no âmbito do PCUS busca esse e somente esse objetivo.
Quaisquer que sejam as premissas a serem feitas em relação à implementação do núcleo da interpretação dos autômatos do ambiente do VKPa, todas essas seriam "especulações", porque o resultado do trabalho de programas automáticos não deve estar associado à implementação de um modelo computacional. Pode ser software (como é agora) ou hardware (como espero no futuro), implementado em um núcleo ou em seu conjunto, na versão de thread único ou multi-thread, etc. etc. tudo isso de forma alguma deve afetar os resultados dos programas automáticos paralelos.

E, ao que parece, o objetivo foi alcançado. O modelo de gatilho RS, como um dos possíveis testes de sistemas de paralelismo [8], nos convence disso ... Como a vida demonstrou, todos os outros programas paralelos, desde que o ambiente tenha passado com êxito na implementação do paralelismo de teste de gatilho RS, funcionam da maneira correta, confiável e estável . A propósito, o mesmo "teste de disparo RS" do MATLAB não passou, e isso já diz muito ...

Literatura
1. . ++ . . . . .. – .: , 2012. – 672 .
2. . : . . – .: , 1981. – 360 .
3. . [ ], : habr.com/ru/post/484588 . . . ( 07.01.2020).
4. .. . .: , 1962.
5. .., - .. . – 2- ., . . – .: , 1988. – 480 .
6. .. . [ ], : cloud.mail.ru/public/HwsK/T95PMM8Ed . . . ( 01.02.2020).
7. . . 2- . – .: , 2004. – 432.
8. .. ? “ ”, №10/97, .116-119. [ ], : www.osp.ru/pcworld/1997/10/158015 . . . ( 01.02.2020).

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


All Articles