并发,协程,事件机器,...实时数学

并行计算着迷于其行为的意外性。但是流程的联合行为并非不可预测。只有在这种情况下,才能通过他的怪癖来研究和理解他。现代的多线程并发是独一无二的。从字面上看。这就是他所有的本质。可以而且应该受到影响的本质。本质,应该以一种好的方式进行了长期的改变。

尽管还有另一种选择。无需进行任何更改和/或影响。设置多线程和协程,使其成为...和并行自动编程(AP)。让他们竞争,并在必要和可能时互相补充。从这个意义上讲,现代并行性至少具有一个优点-它使您能够做到这一点。

好吧,让我们竞争吧!

1.从串行到并行


考虑编写最简单的算术方程:

C2 = A + B + A1 + B1;(1)

让一些实现简单算术运算的块成为现实。在这种情况下,求和块就足够了。对块的数量,它们的结构以及它们之间的关系的清晰而准确的认识就给了米饭。1.在图2中。给出了VKP(a)介质的配置用于求解方程式(1)。

图片
图。1。过程的结构模型

但是图1中的结构图对应于一个由三个方程组成的系统:

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

同时(2),这是方程式(1)的并行实现,方程式(1)是一种用于对数字数组求和的算法,也称为加倍算法。在此,数组由四个数字A,B,A1,B1表示,变量C和C1是中间结果,而C2是数组的总和。

图片
图2。用于配置三个并行过程的对话框的类型,

实现特征包括操作的连续性(当输入数据的任何更改导致重新计算结果时)。更改输入数据后,将需要两个时钟周期,并且当块串联连接时,将在三个时钟周期内获得相同的效果。阵列越大,速度增益越大。

2.并发问题


如果您将被称为支持特定并行解决方案的大量参数,不要感到惊讶,但是对于普通顺序编程中完全不存在的可能问题,他们将保持沉默。正确解释并行性问题的类似解释的主要原因。他们对她说的最少。如果有的话,他们说。我们将在与并行访问数据有关的部分中进行介绍。

任何过程都可以表示为许多连续的不可分割的步骤。对于许多过程,在每个这样的步骤中,同时执行属于所有过程的动作。在下面的基本示例中,我们很可能会遇到一个问题。

假设有两个并行过程对应于以下方程组:

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

假设为变量a,b,c分配了初始值1、1、0。我们可以预期这五个步骤的计算协议如下:

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

在形成它时,我们从以下事实开始:运算符在一个离散量度(步骤)中并行(同时)执行。对于循环语句,它将是循环的迭代。我们还可以假设在计算过程中变量的值固定在离散量度的开头,而变量的变化发生在变量的末尾。这需要一些时间才能完成操作,这与实际情况完全一致。它通常与给定块固有的延迟相关。

但是,最有可能的是,您将获得以下协议:

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

它等效于一个循环中执行两个连续语句的进程的工作:

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

但是,可能会发生语句的执行完全相反的情况,然后协议如下:

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

在多线程编程中,情况甚至更糟。在没有流程同步的情况下,不仅很难预测操作员的启动顺序,而且他们的工作也会在任何地方中断。所有这些都只能影响操作员共同努力的结果。

在AP技术的框架内,简单而正确地允许使用通用过程变量。在这里,大多数情况下,不需要特殊的工作即可同步流程并使用数据。但是有必要挑选出在条件上是瞬时且不可分割的动作,并创建自动过程模型。在我们的例子中,这些动作将是求和运算符,具有循环过渡的自动机将负责它们的启动。
清单1显示了实现sum操作的过程的代码。它的模型是一个有限状态机(见图3),具有一个状态和一个无条件的循环转移,对此,唯一的作用y1(执行两个变量求和的结果)将结果放在第三个变量中。

图片
图3。求和操作的自动化模型

清单1.用于求和运算的自动机过程的实现
#include "lfsaappl.h"
class FSumABC :
    public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FSumABC(nameFsa); }
    bool FCreationOfLinksForVariables();
    FSumABC(string strNam);
    CVar *pVarA;		//
    CVar *pVarB;		//
    CVar *pVarC;		//
    CVar *pVarStrNameA;        //
    CVar *pVarStrNameB;        //
    CVar *pVarStrNameC;        //
protected:
    void y1();
};

#include "stdafx.h"
#include "FSumABC.h"

static LArc TBL_SumABC[] = {
    LArc("s1",	"s1","--", "y1"),
    LArc()
};

FSumABC::FSumABC(string strNam):
    LFsaAppl(TBL_SumABC, strNam, nullptr, nullptr)
{ }

bool FSumABC::FCreationOfLinksForVariables() {
    pVarA = CreateLocVar("a", CLocVar::vtBool, "variable a");
    pVarB = CreateLocVar("b", CLocVar::vtBool, "variable c");
    pVarC = CreateLocVar("c", CLocVar::vtBool, "variable c");
    pVarStrNameA = CreateLocVar("strNameA", CLocVar::vtString, "");
    string str = pVarStrNameA->strGetDataSrc();
    if (str != "") { pVarA = pTAppCore->GetAddressVar(pVarStrNameA->strGetDataSrc().c_str(), this); }
    pVarStrNameB = CreateLocVar("strNameB", CLocVar::vtString, "");
    str = pVarStrNameB->strGetDataSrc();
    if (str != "") { pVarB = pTAppCore->GetAddressVar(pVarStrNameB->strGetDataSrc().c_str(), this); }
    pVarStrNameC = CreateLocVar("strNameC", CLocVar::vtString, "");
    str = pVarStrNameC->strGetDataSrc();
    if (str != "") { pVarC = pTAppCore->GetAddressVar(pVarStrNameC->strGetDataSrc().c_str(), this); }
    return true;
}

void FSumABC::y1() { 
    pVarC->SetDataSrc(this, pVarA->GetDataSrc() + pVarB->GetDataSrc()); 
}


重要的,甚至是必要的,这就是使用CPSU的环境变量。它们的“阴影属性”确保了过程的正确交互。此外,该环境允许您更改其操作模式,但不将变量记录在中间影子存储器中。对该模式下获得的协议进行分析,我们可以验证使用阴影变量的必要性。

3.和协程?


知道以Kotlin语言表示的协程将如何完成任务将非常高兴。让我们以[1]的讨论中考虑的程序作为解决方案模板。它具有易于还原为所需外观的结构。为此,用数字类型替换其中的逻辑变量,然后添加另一个变量,我们将使用求和运算代替逻辑运算。清单2显示了相应的代码。

清单2. Kotlin并行求和程序
import kotlinx.coroutines.*

suspend fun main() =
    // Structured concurrency: if any child coroutine fails,
    // everything else will be cancelled
    coroutineScope {
        var a = 1
        var b = 1
        var c = 0;
        // Use default thread pool 
        withContext(Dispatchers.Default) {
            for (i in 0..4) {
               var res = listOf(async { a+b }, async{ b+c }).map { it.await() }
			c = res[0]
			a = res[1]
               println("$a, $b, $c")
            }
        }
    }


对该程序的结果没有任何疑问,因为 它与上述协议中的第一个完全匹配。

但是,源代码的转换并不像看起来那样明显,因为 使用以下代码片段似乎很自然:

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

如测试所示(可以在Kotlin网站kotlinlang.org/#try-kotlin上在线完成),其使用会导致完全无法预测的结果,并且随着发射的进行而变化。只有对源程序进行更仔细的分析才能得出正确的代码。

从程序的功能的角度来看,包含错误的代码,但从语言的角度来看,它是合法的,这使我们担心该程序在其上的可靠性。这种观点可能会受到Kotlin专家的质疑。但是,容易犯错的原因(这只能通过对“协程编程”的缺乏理解而无法解释)始终坚持要求得出这样的结论。

4. Qt中的事件机器


早先,我们确定事件自动机不是其经典定义中的自动机。他是好是坏,但在某种程度上,事件机器仍然是经典机器的亲戚。它是遥远还是近在咫尺,但我们必须直接谈论它,以免对此产生误解。我们在[2]中开始讨论此问题,但所有这些都是为了继续进行。现在,我们将通过检查Qt中使用事件机器的其他示例来完成此操作。

当然,事件自动机可以看作是经典自动机的退化情况,其中经典自动机具有与事件相关的不确定和/或可变周期持续时间。上一篇文章仅解决了一个例子,并且给出了一个相当具体的示例(请参见详细信息[2]),这表明了这种解释的可能性。接下来,我们将尝试消除这种差距。

Qt库仅将机器转换与事件相关联,这是一个严重的限制。例如,在相同的UML语言中,转换不仅与称为启动事件的事件相关,而且还与保护条件(接收到事件后计算出的逻辑表达式)相关联[3]。在MATLAB中,情况得到了进一步缓解,听起来像是:“如果未指定事件的名称,则在发生任何事件时都将发生转换” [4]。但是,到处都是这种事件的根本原因。但是,如果没有事件怎么办?

如果没有事件,则...您可以尝试创建它们。清单3和图 图4演示了如何使用VKPa环境的自动机类LFsaAppl的后代作为事件Qt类的“包装器”来执行此操作。在此,具有自动机空间离散时间周期的动作y2发送一个信号,该信号启动Qt自动机过渡的开始。后者使用s0Exited方法启动操作y1,该操作实现求和操作。请注意,事件机器是在检查LFsaAppl类的局部变量的初始化之后严格由操作y3创建的。

图片
图4。经典机器和事件机器的结合

清单3.使用事件自动机实现求和模型
#include "lfsaappl.h"
class QStateMachine;
class QState;

class FSumABC :
    public QObject,
    public LFsaAppl
{
    Q_OBJECT
...
protected:
    int x1();
    void y1(); void y2(); void y3(); void y12();
signals:
    void GoState();
private slots:
    void s0Exited();
private:
    QStateMachine * machine;
    QState * s0;
};

#include "stdafx.h"
#include "FSumABC.h"
#include <QStateMachine>
#include <QState>

static LArc TBL_SumABC[] = {
    LArc("st",	"st","^x1",	"y12"),
    LArc("st",	"s1","x1",	"y3"),
    LArc("s1",	"s1","--",	"y2"),
    LArc()
};

FSumABC::FSumABC(string strNam):
    QObject(),
    LFsaAppl(TBL_SumABC, strNam, nullptr, nullptr)
{ }
...
int FSumABC::x1() { return pVarA&&pVarB&&pVarC; }

void FSumABC::y1() {
    pVarC->SetDataSrc(this, pVarA->GetDataSrc() + pVarB->GetDataSrc());
}
//
void FSumABC::y2() { emit GoState(); }
//
void FSumABC::y3() {
    s0 = new QState();
    QSignalTransition *ps = s0->addTransition(this, SIGNAL(GoState()), s0);
    connect (s0, SIGNAL(entered()), this, SLOT(s0Exited()));
    machine = new QStateMachine(nullptr);
    machine->addState(s0);
    machine->setInitialState(s0);
    machine->start();
}
//    
void FSumABC::y12() { FInit(); }

void FSumABC::s0Exited() { y1(); }


上面,我们实现了一个非常简单的机器。或者,更确切地说,将经典和事件自动机结合起来。如果将FSumABC类的先前实现替换为创建的FSumABC类,则应用程序将完全没有区别。但是对于更复杂的模型,事件驱动自动机的有限属性开始充分体现出来。至少已经在创建模型的过程中。清单4以事件自动机的形式显示了AND-NOT元素的模型的实现(有关AND-NOT元素的已使用自​​动机模型的更多详细信息,请参见[2])。

清单4.实现元素模型,而不是由事件机器实现
#include <QObject>

class QStateMachine;
class QState;

class MainWindow;
class ine : public QObject
{
    Q_OBJECT
public:
    explicit ine(MainWindow *parent = nullptr);
    bool bX1, bX2, bY;
signals:
    void GoS0();
    void GoS1();
private slots:
    void s1Exited();
    void s0Exited();
private:
    QStateMachine * machine;
    QState * s0;
    QState * s1;
    MainWindow *pMain{nullptr};
friend class MainWindow;
};

#include "ine.h"
#include <QStateMachine>
#include <QState>
#include "mainwindow.h"
#include "ui_mainwindow.h"

ine::ine(MainWindow *parent) :
    QObject(parent)
{
    pMain = parent;
    s0 = new QState();
    s1 = new QState();

    s0->addTransition(this, SIGNAL(GoS1()), s1);
    s1->addTransition(this, SIGNAL(GoS0()), s0);

    connect (s0, SIGNAL(entered()), this, SLOT(s0Exited()));
    connect (s1, SIGNAL(entered()), this, SLOT(s1Exited()));


    machine = new QStateMachine(nullptr);
    machine->addState(s0);
    machine->addState(s1);
    machine->setInitialState(s1);
    machine->start();
}

void ine::s1Exited() {
    bY = !(bX1&&bX2);
    pMain->ui->checkBoxY->setChecked(bY);
}

void ine::s0Exited() {
    bY = !(bX1&&bX2);
    pMain->ui->checkBoxY->setChecked(bY);
}


很明显,Qt中的事件自动机严格基于摩尔自动机。这限制了模型的功能和灵活性,因为动作仅与状态相关联。结果,例如,对于图1所示的自动机,不可能在从0状态到1状态的两个转变之间进行区分。 4 in [2]。

当然,要实现Miles,可以使用众所周知的过程切换到Moore机器。但是,这导致状态数量增加,并且消除了模型状态与其动作结果之间的简单,可视和有用的关联。例如,经过这样的转换后,必须将Moore自动机的两个状态与[2]中模型1的输出的单个状态进行匹配。

在更复杂的模型上,过渡条件的问题开始显而易见。为了绕过考虑的AND-NOT元素的软件实现,在模型控制对话框中内置了对输入通道状态的分析,如清单5所示。

清单5. NAND元素控制对话框
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "ine.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    pine = new ine(this);
    connect(this, SIGNAL(GoState0()), pine, SIGNAL(GoS0()));
    connect(this, SIGNAL(GoState1()), pine, SIGNAL(GoS1()));
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_checkBoxX1_clicked(bool checked)
{
    bX1 = checked;
    pine->bX1 = bX1;
    bY = !(bX1&&bX2);
    if (!(bX1&&bX2)) emit GoState0();
    else emit GoState1();
}

void MainWindow::on_checkBoxX2_clicked(bool checked)
{
    bX2 = checked;
    pine->bX2 = bX2;
    bY = !(bX1&&bX2);
    if (!(bX1&&bX2)) emit GoState0();
    else emit GoState1();
}


总而言之,以上所有内容显然使模型变得复杂,并在理解其工作时产生了问题。此外,在考虑本地解决方案时,此类本地解决方案可能无法正常工作。在这种情况下,一个很好的例子是尝试创建一个RS触发模型(有关RS触发的两分量自动机模型的更多详细信息,请参见[5])。但是,所获成果的乐趣将传递给赛事机器的爱好者。如果...除非他们当然成功;​​)

5.二次函数和...蝴蝶?


将输入数据表示为外部并行过程很方便。在此上方是用于管理AND-NOT元素模型的对话框。此外,数据的变化速率会显着影响结果。为了证实这一点,我们考虑二次函数y =ax²+ bx + c的计算,我们以一组并行功能和相互作用的块的形式来实现。

已知二次函数的图具有抛物线的形状。但是,严格来讲,数学家所描绘的抛物线与示波器将显示的抛物线将永远无法匹配。原因是数学家经常在瞬时类别中思考,认为输入量的变化会立即导致函数的计算。但是在现实生活中,这根本不是真的。功能图的外观将取决于计算器的速度,输入数据的变化率等。等等是的,程序本身的速度可能会有所不同。这些因素将影响函数的形式,在某种情况下,以这种形式将很难猜测抛物线。我们将进一步相信这一点。

因此,我们将乘法,除法和求幂的块添加到求和块。有了这样的集合,我们可以“收集”任何复杂的数学表达式。但是我们也可以拥有实现更复杂功能的“立方体”。图5。二次函数的实现有两种版本:多块(参见带有标签的箭头-1,也参见图6)和一个块的一种变体(在图5中带有标签2的箭头)。

图片
图5。实现二次函数的两个选项

图片
图6。用于计算二次函数的结构模型

图5。似乎是“模糊”(请参见标记为3的箭头),如果适当放大(请参见标记为4的图表(趋势)),则可以确定问题不在图形属性中。这是时间对变量计算的影响的结果:变量y1是多块变量的输出值(图形的红色),变量y2是单块变量的输出值(黑色)。但是这两个图形都与“抽象图形” [y2(t),x(t-1)](绿色)不同。后者是为变量y2的值和延迟一个时钟周期的输入变量的值构造的(请参阅名称为x [t-1]的变量)。

因此,输入函数x(t)的变化率越高,“模糊效果”将越强,并且曲线图y1,y2与曲线图[y2(t),x(t-1)]越远。检测到的“缺陷”可用于您自己的目的。例如,没有什么可以阻止我们向输入施加正弦信号。当方程的第一个系数也以类似的方式变化时,我们将考虑一个更复杂的选择。实验结果显示了VKPa培养基的屏幕,如图2所示。 7.

图片
图7。正弦输入信号的仿真结果

左下方的屏幕显示了提供给二次函数实现输入的信号。上面是输出值y1和y2的图形。以``翅膀''形式的图表是在两个坐标中绘制的值。因此,借助二次函数的各种实现,我们绘制了“蝴蝶”的一半。绘制一个整体是技术问题。

但是并行性的悖论并没有就此结束。在图。图8显示了自变量x的“反向”变化趋势。它们已经传递到“抽象”图的左侧(我们注意到后者没有改变位置!)。

图片
图。 8.具有输入信号的正向和反向线性变化的图的类型

在此示例中,输出信号相对于其“瞬时”值的“双倍”误差变得明显。计算系统越慢或信号频率越高,误差越大。正弦波是输入信号的正向和反向变化的示例。因此,图4中的“机翼”采用了这种形式。没有“后向误差”效应,它们的宽度将缩小两倍。

6.自适应PID控制器


让我们再考虑一个示例,其中显示了所考虑的问题。在图。图9显示了对自适应PID控制器建模时VKP(a)介质的配置。还显示了一个方框图,其中PID控制器由名为PID的单元表示。在黑盒级别,它类似于先前考虑的二次函数的单块实现。

将某个数学包中的PID控制器模型的计算结果与从VKP(a)环境中的仿真结果获得的协议进行比较的结果如图10所示,其中红色图为计算值,蓝色图为协议。它们不匹配的原因是,如进一步分析所示,在数学包的框架内进行的计算对应于对象首先执行PID控制器并停止然后是对象模型等时对象的顺序操作。在循环。当模型和对象并行工作时,VKPa环境根据实际情况实现/建模对象的并行操作。

图片
图9。 PID控制器的实现

图片
图10。计算值与PID控制器仿真结果的比较

正如我们已经宣布的那样,由于VKP(a)具有模拟结构块顺序运行的模式,因此不难验证PID控制器的顺序计算模式的假设。将介质的工作模式更改为串行,我们得到了图表的重合,如图11所示。

图片
图11。PID控制器和控制对象的顺序操作

7.结论


在CPSU(a)的框架内,计算模型赋予程序具有真正“活”对象特征的属性。因此,与“生命数学”的象征联系。如实践所示,我们在模型中仅考虑了现实生活中不能忽略的那些问题。在并行计算中,这主要是计算的时间和有限性。当然,尽管不能忘记[自动]数学模型对一个或另一个“活”对象的充分性。

与无法战胜的斗争是不可能的。是时候了。这只有在童话中才有可能。但是考虑到和/或什至将其用于您自己的目的是有意义的。在现代并行编程中,忽略时间会导致许多难以控制和检测到的问题-信号竞速,死锁过程,同步问题等。等等VKP(a)技术在很大程度上避免了此类问题,仅因为它包括一个实时模型并考虑了计算过程的有限性。它包含大多数类似物所忽略的内容。

结论。蝴蝶是蝴蝶,但是例如,您可以考虑一个二次函数和线性函数的方程组。为此,将线性函数模型和控制其重合的过程添加到已创建的模型中就足够了。因此,将通过建模找到解决方案。它很可能不像分析那样准确,但是可以更简单,更快速地获得它。在许多情况下,这已绰绰有余。通常,寻找分析解决方案通常是一个悬而未决的问题。
关于后者,召回了AVM。对于那些不是最新的或忘记了的人,-模拟计算机。结构性原则在许多方面都是通用的,并且是寻找解决方案的方法。

应用


1)视频youtu.be/vf9gNBAmOWQ

2)示例存档github.com/lvs628/FsaHabr/blob/master/FsaHabr.zip

3)必要的Qt dll库版本5.11.2的存档github.com/lvs628/FsaHabr/blob/master/QtDLLs.zip

在Windows 7环境中开发了示例,要安装它们,请打开示例存档,如果没有安装Qt或当前的Qt版本与5.11.2版本不同,然后另外打开Qt存档并将路径写入Path环境变量中的库。接下来,运行\ FsaHabr \ VCPaMain \ release \ FsaHabr.exe并使用对话框选择示例的配置目录,例如\ FsaHabr \ 9.ParallelOperators \ Heading1 \ Pict1.C2 = A + B + A1 + B1 \(请参见还有视频)。

评论。在第一次启动时,可能会出现一个文件选择对话框,而不是目录选择对话框。我们还选择配置目录和其中的一些文件,例如vSetting.txt。如果根本没有出现配置选择对话框,则在启动之前,删除FsaHabr.exe文件所在目录中的ConfigFsaHabr.txt文件。

为了不重复在“内核:自动空格”对话框(可以使用菜单项:FSA-tools / Space Management / Management打开)中选择配置,请单击“记住目录路径”按钮,然后取消选择“启动时显示配置选择对话框” ”。将来,要选择其他配置,将需要再次设置此选择。

文献


1. NPS,传送带,自动计算以及协程。 [电子资源],访问方式:habr.com/en/post/488808免费。亚兹俄语(治疗日期02.22.2020)。
2. 自动机是一件大事吗? [电子资源],访问方式:habr.com/en/post/483610免费。亚兹俄语(治疗日期02.22.2020)。
3. BUCH G.,RAMBO J.,Jacobson I. UML。用户说明书。第二版。 IT学院:莫斯科,2007年-493页
4.罗加切夫Stateflow V5。用户说明书。 [电子资源],访问模式:bourabai.kz/cm/stateflow.htm免费。亚兹俄语(发布日期10.04.2020)。
5,并行计算模型 [电子资源],访问方式:habr.com/en/post/486622免费。亚兹 俄语 (发行日期04/11/2020)。

All Articles