基于事件的c ++程序状态机与协同程序

时间:2015-10-12 20:43:17

标签: c++ coroutine state-machine

c ++中的协同实例是一种非常强大的实现状态机的技术,但我在互联网上找到的例子过于简单,例如:它们通常代表某种迭代器,它在调用一些“Next”例程后移动,仅依赖于协程的初始参数。然而,在相当复杂的基于事件的状态机中,每个下一步取决于所接收的导致恢复运行的特定事件,并且还应当针对可能在任何时间发生的事件实现一些默认事件处理程序。

假设我们有一个简单的手机状态机。

  

状态:HOOK OFF - > [EVT:DIAL TONE] - > [状态:拨号] - > [EVT:NUMBER DIALED] - > STATE:TALKING

现在我想要一个会看到类似的协程。

PhoneSM()
{ 
HookOf(); 
Yield_Till(DialTone_Event); 
Dial(); 
Yield_Till(EndOfDial_Event); 
Talk(); 
...
}

e.g。要求

  1. Yield_Till只会在收到特定事件时(如何???)继续运行couroutine运行。如果没有那么它应该再次产生。

  2. Yield_Till必须知道如何将事件运行到像Hangup_Event这样的默认处理程序,因为它确实可以随时发生,每次添加yield调用都很麻烦。

  3. 我们非常感谢您对c ++(仅限!!!)实施或现成基础设施的任何帮助以满足要求。

3 个答案:

答案 0 :(得分:1)

在我看来,您正在尝试将事件驱动的状态机编码为顺序流程图。状态图和流程图之间的区别非常重要,例如在文章"A Crash Course in UML State Machines"中进行了解释:

状态机应该被编码为一次性函数,用于处理当前事件,返回而不会产生或阻塞。如果您希望使用协同例程,可以从协同例程调用此状态机函数,然后在每个事件之后生成。

答案 1 :(得分:0)

大多数协同常规库都没有关闭复杂的yield函数。他们只是屈服,你的共同例程将在某个任意点得到控制。因此,在收益之后,您将不得不在代码中测试适当的条件,如果不满足则再次屈服。在这段代码中,您还可以对挂起等事件进行测试,在这种情况下,您将终止您的协同例程。

公共域中有许多实现,某些操作系统(例如Windows)提供协同例程服务。只需谷歌合作例程或光纤。

答案 2 :(得分:0)

这是一个古老的问题,但是我在搜索如何使用C ++ 20协程实现这一点时发现的。由于我已经用不同的方法实施了几次,所以我仍然尝试为将来的读者回答。

首先了解一些背景,为什么这实际上是一个状态机。如果只对如何实现感兴趣,可以跳过这一部分。状态机是作为执行代码的标准方法而引入的,该方法偶尔会随着新事件而被调用并进入一些内部状态。因为在这种情况下,程序计数器和状态变量显然不能存在于寄存器中,而在堆栈上,还需要一些其他代码才能继续到您离开的地方。状态机是实现此目标的标准方法,而不会产生过多开销。但是,可以为同一任务编写协程,并且可以在这样的协程中传输每个状态机,其中每个状态是一个标签,事件处理代码以转到下一个状态的结尾结束,并在该状态下产生该状态。每个开发人员都知道goto代码是意大利面条代码,并且有一种更简洁的方法来用流程控制结构表达意图。实际上,我还没有看到使用协程和流控制无法以更紧凑,更易于理解的方式编写的状态机。话虽这么说:如何在C / C ++中实现?

有几种方法可以进行协程:可以在Duff's device之类的循环中使用switch语句来完成,现在POSIX coroutines已过时并已从标准和C +中删除+20带来了基于C ++的现代协程。为了拥有完整的事件处理状态机,还有一些其他要求。首先,协程必须产生一系列事件,并将继续下去。然后,需要一种将实际发生的事件及其参数传递回协程的方法。最后,必须有一些驱动程序代码来管理事件,并在等待的事件上注册事件处理程序,回调或信号插槽连接,并在发生此类事件时调用协程。

在我的最新实现中,我使用了驻留在协程内部并由引用/指针产生的事件对象。通过这种方式,协程可以决定何时对此类事件感兴趣,即使该协程可能不在能够对其进行处理的状态下(例如,对先前发送的请求的响应得到了答复,但答案却没有待处理)。它还允许使用不同的事件类型,这些事件类型可能需要不同的方法来侦听与所使用的驱动程序代码无关的事件(可以通过这种方式进行简化)。

这里是问题中的状态机的一个小达夫装置协程(带有一个额外的占用事件用于演示):

app.get("/claims", function (req, res) {
  res.setHeader("Access-Control-Allow-Origin", "*");
  const { spawn } = require("child_process");
  const pyProg = spawn("python",["claims.py"]);
pyProg.stdout.on("data", function (data) {
 return res.send(JSON.parse(JSON.stringify(data.toString())));
  });
});
var server = app.listen(8000, function () {
  console.log("App running on port 8000");
});
server.timeout = 0;

当然,实现所有协程处理会使这过于复杂。真正的协程会简单得多。以下是伪代码:

class PhoneSM
{
    enum State { Start, WaitForDialTone, WaitForEndOfDial, … };
    State state = Start;
    std::unique_ptr<DialTone_Event> dialToneEvent;
    std::unique_ptr<EndOfDial_Event> endOfDialEvent;
    std::unique_ptr<Occupied_Event> occupiedEvent;
public:
    std::vector<Event*> operator()(Event *lastEvent = nullptr)
    {
        while (1) {
            switch (state) {
            case Start:
                HookOf();

                dialToneEvent = std::make_unique<DialTone_Event>();
                state = WaitForDialTone;
                // yield ( dialToneEvent )
                return std::vector<Event*>{ dialToneEvent.get() };
            case WaitForDialTone:
                assert(lastEvent == dialToneEvent);
                dialToneEvent.reset();

                Dial();

                endOfDialEvent = std::make_unique<EndOfDial_Event>();
                occupiedEvent = std::make_unique<Occupied_Event>();
                state = WaitForEndOfDial;
                // yield ( endOfDialEvent, occupiedEvent )
                return std::vector<Event*>{ endOfDialEvent.get(), occupiedEvent.get() };
            case WaitForEndOfDial:
                
                if (lastEvent == occupiedEvent) {
                    // Just return from the coroutine
                    return std::vector<Event*>();
                }
                assert(lastEvent == endOfDialEvent);
                occupiedEvent.reset();
                endOfDialEvent.reset();

                Talk();

                …
            }
        }
    }
}