通过延迟的自我转换来增强MSM并行行为? [英] Boost MSM parallel behavior with delayed self-transitions?

查看:135
本文介绍了通过延迟的自我转换来增强MSM并行行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Boost MSM(基本和函子前端),并试图实现以下状态机:

I am using Boost MSM (basic and functor front-ends) and am trying to implement the following state machine:

换句话说:

  1. 进入状态State1
  2. 进入状态A并执行action_A. 2秒钟后,打印正在重试..."并重新执行状态A(即调用其输入操作).这会永远循环下去...
  3. 与2同时显示(即并行"),进入状态B并执行action_B. 5秒钟后,打印正在重试..."并重新执行状态B(即调用其进入操作).这会永远循环下去...

我想知道在Boost MSM中创建此状态机的方法.这里有两个技巧,我不知道该怎么做:

I would like to know the way to create this state machine in Boost MSM. There are two tricks here which I cannot figure out how to do:

  • 并行执行(即,运行action_A不会同时停止运行action_B)
  • 延迟的转换(即,在状态A的2秒后和状态B的5秒后发生的转换).延迟都不应阻塞!这段时间过后,过渡应该只是触发".

非常感谢您的帮助.

修改

@TakatoshiKondo答案可以满足我的要求,但是我想对答案的某些部分进行更多说明,以便完全理解它.

@TakatoshiKondo answer does what I need, but I'd like to have more explanation on certain parts of the answer in order to understand it fully.

  1. 这与pthreads实现相比如何?您是否认为Boost.Asio比将状态A和B置于不同的线程中并在每个线程中具有阻塞的被动等待(例如通过unistd.husleep(useconds_t usec)可以实现的)更好的解决方案?我的感觉是,我没有尝试将其与Boost.MSM一起使用的pthreads将是一种更通用/更少约束的实现?
  2. 我不清楚createprocess方法的工作方式(为什么create函数需要可变参数模板?).特别是,我以前没有使用过智能指针或std::forward,因此,如果您可以对这些函数中的每一行进行人为的解释,那就太好了(我很不方便按顺序阅读这些功能,尝试理解此代码).
  3. 在第2章中,对Smwpios成员变量的用途的更好的解释将是很好的.使用ios指针有意满足副本构造函数的含义是什么?此外,我看不到在构造函数Sm(boost::asio::io_service* ios) : ios(ios) {}中的任何地方设置ios,这似乎从未调用过?
  4. State1_前端内部,在三个on_entry方法中有三个BOOST_STATIC_ASSERT调用.这些在做什么?
  5. main()函数中,我能够删除auto t = std::make_shared<boost::asio::deadline_timer>(ios);行而无需更改行为-多余吗?
  1. How does this compare with a pthreads implementation? Do you think Boost.Asio is a better solution than putting the states A and B into different threads and having blocking, passive waits in each (such as what could be achieved via usleep(useconds_t usec) of unistd.h)? My feeling is that pthreads, which I have not tried using with Boost.MSM, would be a more generic/less constrained implementation?
  2. I am not clear on how the create and process methods work (why does the create function need a variadic template?). In particular, I've not previously worked with smart pointers or std::forward, so if you could give a human explanation of each line in these functions it would be great (I'm short on time to read about these features generically in order to try to understand this code).
  3. In hand with 2, a better explanation of the purpose of the wp and ios member variables of Sm would be great. What do you mean by using ios pointer to intentionally meet copy constructor? I furthermore do not see ios being set anywhere but in the constructor Sm(boost::asio::io_service* ios) : ios(ios) {}, which it seems that you never call?
  4. Inside the State1_ front-end, you have three BOOST_STATIC_ASSERT calls in the three on_entry methods. What are these doing?
  5. In the main() function, I was able to delete the line auto t = std::make_shared<boost::asio::deadline_timer>(ios); without changing the behaviour - was it redundant?

推荐答案

以下是完成此操作的完整代码示例:

Here is a complete code example to do that:

// g++ example.cpp -lboost_system

#include <iostream>

#include <boost/asio.hpp>

#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>

namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;


// ----- State machine
struct Sm : msmf::state_machine_def<Sm> {
    using back = msm::back::state_machine<Sm>;

    template <typename... T>
    static std::shared_ptr<back> create(T&&... t) {
        auto p = std::make_shared<back>(std::forward<T>(t)...);
        p->wp = p; // set wp after creation.
        return p;
    }

    template <typename Ev>
    void process(Ev&& ev) {
        // process_event via backend weak_ptr
        wp.lock()->process_event(std::forward<Ev>(ev));
    }

    // ----- Events
    struct EvSetParent {};
    struct After2 {};
    struct After5 {};

    Sm(boost::asio::io_service* ios):ios(ios) {}
    struct State1_:msmf::state_machine_def<State1_> {
        template <class Event,class Fsm>
        void on_entry(Event const&, Fsm& f) const {
            BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, Sm>::value));
            std::cout << "State1::on_entry()" << std::endl;
            f.process(EvSetParent());
        }

        struct Action {
            template <class Event, class Fsm, class SourceState, class TargetState>
            void operator()(Event const&, Fsm&, SourceState&, TargetState&) const {
                std::cout << "Trying again..." << std::endl;
            }
        };

        struct A:msmf::state<> {
            template <class Event,class Fsm>
            void on_entry(Event const&, Fsm& f) const {
                BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
                std::cout << "A::on_entry()" << std::endl;
                auto t = std::make_shared<boost::asio::deadline_timer>(*f.parent->ios);
                t->expires_from_now(boost::posix_time::seconds(2));
                t->async_wait([t, &f](boost::system::error_code const) {
                        f.parent->process(After2());
                    }
                );
            }
        };

        struct B:msmf::state<> {
            template <class Event,class Fsm>
            void on_entry(Event const&, Fsm& f) const {
                BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
                std::cout << "B::on_entry()" << std::endl;
                auto t = std::make_shared<boost::asio::deadline_timer>(*f.parent->ios);
                t->expires_from_now(boost::posix_time::seconds(5));
                t->async_wait([t, &f](boost::system::error_code const) {
                        f.parent->process(After5());
                    }
                );
            }
        };

        // Set initial state
        typedef mpl::vector<A, B> initial_state;
        // Transition table
        struct transition_table:mpl::vector<
            //          Start  Event   Next       Action      Guard
            msmf::Row < A,     After2, A,         Action,     msmf::none >,
            msmf::Row < B,     After5, B,         Action,     msmf::none >
        > {};

        Sm* parent;
    };

    typedef msm::back::state_machine<State1_> State1;

    // Set initial state
    typedef State1 initial_state;

    struct ActSetParent {
        template <class Event, class Fsm, class SourceState, class TargetState>
        void operator()(Event const&, Fsm& f, SourceState& s, TargetState&) const {
                std::cout << "ActSetIos" << std::endl;
                s.parent = &f; // set parent state machine to use process() in A and B.
        }
    };
    // Transition table
    struct transition_table:mpl::vector<
        //          Start   Event        Next        Action        Guard
        msmf::Row < State1, EvSetParent, msmf::none, ActSetParent, msmf::none >
    > {};

    // front-end can access to back-end via wp.
    std::weak_ptr<back> wp;

    boost::asio::io_service* ios; // use pointer intentionally to meet copy constructible
};


int main() {
    boost::asio::io_service ios;
    auto t = std::make_shared<boost::asio::deadline_timer>(ios);

    auto sm = Sm::create(&ios);

    ios.post(
        [&]{
            sm->start();
        }
    );

    ios.run();
}

让我们来挖掘代码.

Boost.MSM不支持延迟事件触发机制.因此我们需要一些计时器处理机制.我选择Boost.Asio截止时间计时器.它可以与事件驱动的库(例如Boost.MSM)一起很好地工作.

Boost.MSM doesn't support delayed event fire mechanism. So we need to some timer handling mechanism. I choose Boost.Asio deadline timer. It works well with event driven library such as Boost.MSM.

为了在状态机的前端调用process_event(),它需要知道其后端.所以我写了create()函数.

In order to call process_event() in the front-end of the state machine, it needs to know its back-end. So I wrote create() function.

    template <typename... T>
    static std::shared_ptr<back> create(T&&... t) {
        auto p = std::make_shared<back>(std::forward<T>(t)...);
        p->wp = p; // set wp after creation.
        return p;
    }

它创建后端的shared_ptr,然后将其分配给weak_ptr. 如果weak_ptr设置正确,则可以按以下方式调用process_event().我写了一个包装器process().

It creates a shared_ptr of the back-end and then, and assigns it to the weak_ptr. If the weak_ptr set correctly, then I can call process_event() as follows. I wrote a wrapper process().

    template <typename Ev>
    void process(Ev&& ev) {
        // process_event via backend weak_ptr
        wp.lock()->process_event(std::forward<Ev>(ev));
    }

客户端代码按如下所示调用create()函数:

Client code call the create() function as follows:

    auto sm = Sm::create(&ios);

Sm具有成员变量ios来设置截止日期计时器.状态机的前端需要MSM复制.因此ios是io_service的指针,而不是引用.

Sm has the member variable ios to set deadline timer. The front-end of the state-machine is required copyable by MSM. So ios is the pointer of io_service not reference.

状态A和B是正交区域.为了实现正交区域,请将多个初始状态定义为mpl :: vector.

State A and B are orthogonal regions. In order to implement orthogonal regions, define multiple initial states as mpl::vector.

    typedef mpl::vector<A, B> initial_state;

状态A和B是复合状态. MSM使用子计算机状态来实现复合状态.最外面的状态Sm是状态机,而State1_也是状态机.我在状态A和B的进入操作中设置了一个计时器.当计时器被激发时,调用process().但是,processs()Sm的成员函数,而不是State1_.因此,我需要实现某种机制以从Stete1_访问Sm. 我将成员变量parent添加到State1_.它是Sm的指针.在State1_的进入操作中,我调用process(),事件是PEvSetParent . It simply invokes ActSetParent . In the action, SourceState is State1_`.我将父成员变量设置为父指针,如下所示:

State A and B is composite states. MSM uses sub-machine state to implement composite states. Outer most state Sm is a state-machine and State1_ is also state machine. I set a timer in the entry action of state A and B. And when timer is fired, call process(). However, processs() is a member function of Sm, not State1_. So I need to implement some mechanism to access Sm from Stete1_. I added the member variable parent to State1_. It's a pointer of Sm. In the entry action of the State1_, I call process() and the event is PEvSetParent. It simply invokesActSetParent. In the action, SourceState isState1_`. I set parent member variable to parent pointer as follows:

    struct ActSetParent {
        template <class Event, class Fsm, class SourceState, class TargetState>
        void operator()(Event const&, Fsm& f, SourceState& s, TargetState&) const {
                std::cout << "ActSetIos" << std::endl;
                s.parent = &f; // set parent state machine to use process() in A and B.
        }
    };

最后,我可以在状态A和B的作用下调用process().

Finally, I can call process() in the action of the state A and B.

        struct A:msmf::state<> {
            template <class Event,class Fsm>
            void on_entry(Event const&, Fsm& f) const {
                BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
                std::cout << "A::on_entry()" << std::endl;
                auto t = std::make_shared<boost::asio::deadline_timer>(*f.parent->ios);
                t->expires_from_now(boost::posix_time::seconds(2));
                t->async_wait([t, &f](boost::system::error_code const) {
                        f.parent->process(After2());
                    }
                );
            }
        };

修改

  1. 这与pthreads实现相比如何?您是否认为Boost.Asio比将状态A和B置于不同的线程中并在每个线程中具有阻塞的被动等待(例如,可以通过unistd.h的usleep(useconds_t usec)实现的被动等待)更好的解决方案?我的感觉是,我没有尝试将其与Boost.MSM一起使用的pthreads将是一种更通用/更少约束的实现?

Boost.MSM的process_event()不是线程安全的.因此,您需要锁定它.请参见 Boost msm中的线程安全 AFAIK,sleep()/usleep()/nanosleep()是阻止函数.当您在Boost.MSM的操作中调用它们时,这意味着(从头开始)从process_event()调用它们.并且它需要锁定.最后,阻塞等待会互相阻塞(在这种情况下,after2和after5).因此,我认为Boost.ASIO的异步方法更好.

Boost.MSM's process_event() is NOT thread-safe. So you need to lock it. See Thread safety in Boost msm AFAIK, sleep()/usleep()/nanosleep() are blocking functions. When you call them in the action of Boost.MSM, that means they are called (ogirinally) from process_event(). And it requires lock. Finally, blocking wait blocks each other (in this case, after2 and after5). Hence I think that Boost.ASIO's async approch is better.

  1. 我不清楚创建和处理方法的工作方式(为什么create函数需要可变参数模板?).特别是,我以前没有使用过智能指针或std :: forward,因此,如果您可以对这些函数中的每一行进行人为的解释,那就太好了(我很容易花时间阅读这些内容,以尝试理解此代码).

Boost.MSM的后端继承其前端.前端构造函数是Sm(boost::asio::io_service* ios):ios(ios) {}.在这种情况下,构造函数的参数为​​ios.但是,可以根据用例进行更改.函数create()创建back的shared_ptr.并且back的构造函数将所有参数转发到前端.因此,在auto sm = Sm::create(&ios);处的参数ios被转发到Sm的构造函数.我使用可变参数模板和std :: forward的原因是最大程度地提高了灵活性.如果更改了Sm的构造函数的参数,则无需更改create()函数. 您可以按以下方式更改create()函数:

Boost.MSM's backend inherits its frontend. The frontend constructor is Sm(boost::asio::io_service* ios):ios(ios) {}. In this case, the parameter of the constructor is ios. However, it could be changed depends on usecase. The function create() creates a shared_ptr of back. And back's constructor forwards all parameters to frontend. So the argument ios at auto sm = Sm::create(&ios); is forwarded to Sm's constructor. The reason I use variadic templates and std::forward is maximize flexibility. If the parameters of Sm's constructor is changed, I don't need to change create() function. You can change the create() function as follows:

    static std::shared_ptr<back> create(boost::asio::io_service* ios) {
        auto p = std::make_shared<back>(ios);
        p->wp = p; // set wp after creation.
        return p;
    }

此外,create()process()使用&&的模板参数.它们称为转发参考(通用参考).这是一个叫做完美转发的成语. 参见 http://en.cppreference.com/w/cpp/utility/forward

In addition, create() and process() use template parameters that with &&. They are called as forwarding-reference (universal-reference). It is an idiom called perfect-forwarding. See http://en.cppreference.com/w/cpp/utility/forward

  1. 在第2章中,对Sm的wp和ios成员变量的用途进行了更好的解释将是一个很好的解释.使用ios指针有意满足副本构造函数的意思是什么?我还看不到ios,而是在构造函数Sm(boost :: asio :: io_service * ios)中设置ios:ios(ios){},看来您从未调用过它?

Boost.MSM到目前为止不支持转发参考.我写了一个请求请求,请参见 https://github.com/boostorg/msm/pull/8

Boost.MSM doesn't support forwarding-reference, so far. I wrote a pull request See https://github.com/boostorg/msm/pull/8

因此,转发引用会在Boost.MSM中调用copy-constructor.这就是我选择boost :: asio :: io_service的指针的原因.但是,这不是原始问题的重点.如果不使用转发引用,则可以使用Sm中的引用类型.因此,我将代码更新如下:

So forwarding-reference invokes copy-constructor in the Boost.MSM. That is the reason I choose the pointer of boost::asio::io_service. However, it is not an essential point of the original question. If I don't use forwarding-reference, I can use the reference types in Sm. So I update the code as follows:

    static std::shared_ptr<back> create(boost::asio::io_service& ios) {
        auto p = std::make_shared<back>(std::ref(ios));
        p->wp = p; // set wp after creation.
        return p;
    }

std::ref不适用于make_shared.它用于Boost.MSM.由于缺乏转发参考支持,Boost.MSM的构造函数需要指定参考或不指定参考.

std::ref is not for make_shared. It is for Boost.MSM. Boost.MSM's constructor requires specifying reference or not due to lack of the forwarding reference support.

  1. 在State1_前端内部,在三个on_entry方法中有三个BOOST_STATIC_ASSERT调用.这些在做什么?

在运行时不执行任何操作.只需在编译时检查Fsm的类型即可.有时我对Fsm的类型感到困惑.我想读者也可能会感到困惑,所以我将其留在代码中.

It does nothing in the run-time. Just cheking the type of Fsm at the compile-time. Sometimes I got confused the type of Fsm. I guess the readers also might get confused, so I leave it in the code.

  1. 在main()函数中,我能够删除行auto t = std :: make_shared(ios);无需更改行为-是否多余?

啊哈,我忘了抹掉它.我更新了代码.

Aha, I forgot erase it. I update the code.

这是更新的代码:

#include <iostream>

#include <boost/asio.hpp>

#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>

namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;


// ----- State machine
struct Sm : msmf::state_machine_def<Sm> {
    using back = msm::back::state_machine<Sm>;

    static std::shared_ptr<back> create(boost::asio::io_service& ios) {
        auto p = std::make_shared<back>(std::ref(ios));
        p->wp = p; // set wp after creation.
        return p;
    }

    template <typename Ev>
    void process(Ev&& ev) {
        // process_event via backend weak_ptr
        wp.lock()->process_event(std::forward<Ev>(ev));
    }

    // ----- Events
    struct EvSetParent {};
    struct After2 {};
    struct After5 {};

    Sm(boost::asio::io_service& ios):ios(ios) {}
    struct State1_:msmf::state_machine_def<State1_> {
        template <class Event,class Fsm>
        void on_entry(Event const&, Fsm& f) const {
            BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, Sm>::value));
            std::cout << "State1::on_entry()" << std::endl;
            f.process(EvSetParent());
        }

        struct Action {
            template <class Event, class Fsm, class SourceState, class TargetState>
            void operator()(Event const&, Fsm&, SourceState&, TargetState&) const {
                std::cout << "Trying again..." << std::endl;
            }
        };

        struct A:msmf::state<> {
            template <class Event,class Fsm>
            void on_entry(Event const&, Fsm& f) const {
                BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
                std::cout << "A::on_entry()" << std::endl;
                auto t = std::make_shared<boost::asio::deadline_timer>(f.parent->ios);
                t->expires_from_now(boost::posix_time::seconds(2));
                t->async_wait([t, &f](boost::system::error_code const) {
                        f.parent->process(After2());
                    }
                );
            }
        };

        struct B:msmf::state<> {
            template <class Event,class Fsm>
            void on_entry(Event const&, Fsm& f) const {
                BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
                std::cout << "B::on_entry()" << std::endl;
                auto t = std::make_shared<boost::asio::deadline_timer>(f.parent->ios);
                t->expires_from_now(boost::posix_time::seconds(5));
                t->async_wait([t, &f](boost::system::error_code const) {
                        f.parent->process(After5());
                    }
                );
            }
        };

        // Set initial state
        typedef mpl::vector<A, B> initial_state;
        // Transition table
        struct transition_table:mpl::vector<
            //          Start  Event   Next       Action      Guard
            msmf::Row < A,     After2, A,         Action,     msmf::none >,
            msmf::Row < B,     After5, B,         Action,     msmf::none >
        > {};

        Sm* parent;
    };

    typedef msm::back::state_machine<State1_> State1;

    // Set initial state
    typedef State1 initial_state;

    struct ActSetParent {
        template <class Event, class Fsm, class SourceState, class TargetState>
        void operator()(Event const&, Fsm& f, SourceState& s, TargetState&) const {
                std::cout << "ActSetIos" << std::endl;
                s.parent = &f; // set parent state machine to use process() in A and B.
        }
    };
    // Transition table
    struct transition_table:mpl::vector<
        //          Start   Event        Next        Action        Guard
        msmf::Row < State1, EvSetParent, msmf::none, ActSetParent, msmf::none >
    > {};

    // front-end can access to back-end via wp.
    std::weak_ptr<back> wp;

    boost::asio::io_service& ios;
};


int main() {
    boost::asio::io_service ios;

    auto sm = Sm::create(ios);

    ios.post(
        [&]{
            sm->start();
        }
    );

    ios.run();
}

这篇关于通过延迟的自我转换来增强MSM并行行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆