【问题标题】:Boost MSM parallel behavior with delayed self-transitions?通过延迟的自转换来提升 MSM 并行行为?
【发布时间】:2017-03-21 07:29:40
【问题描述】:

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

言辞:

  1. 进入状态State1
  2. 进入状态 A 并执行 action_A。 2 秒后,打印“再次尝试...”并重新执行状态 A(即调用其进入操作)。这永远循环......
  3. 与2同时(即“并行”),进入状态B,执行action_B。 5 秒后,打印“再次尝试...”并重新执行状态 B(即调用其进入操作)。这永远循环......

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

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

非常感谢您的帮助。

编辑

@TakatoshiKondo 答案可以满足我的需要,但我想对答案的某些部分进行更多解释,以便完全理解它。

  1. 这与 pthreads 实现相比如何?您认为 Boost.Asio 是否比将状态 A 和 B 放入不同的线程并在每个线程中进行阻塞、被动等待(例如可以通过 usleep(useconds_t usec)unistd.h 实现的)更好的解决方案?我的感觉是,我没有尝试与 Boost.MSM 一起使用的 pthread 会是一个更通用/更少约束的实现?
  2. 我不清楚createprocess 方法是如何工作的(为什么create 函数需要可变参数模板?)。特别是,我以前没有使用过智能指针或std::forward,所以如果您可以对这些函数中的每一行进行人工解释,那就太好了(我没有时间按顺序阅读这些功能尝试理解这段代码)。
  3. 结合 2,更好地解释 Smwpios 成员变量的用途会很棒。使用ios 指针故意满足复制构造函数是什么意思?此外,我没有看到ios 被设置在构造函数Sm(boost::asio::io_service* ios) : ios(ios) {} 中的任何位置,您似乎从未调用过它?
  4. State1_ 前端中,三个on_entry 方法中有三个BOOST_STATIC_ASSERT 调用。他们在做什么?
  5. main() 函数中,我能够在不改变行为的情况下删除auto t = std::make_shared<boost::asio::deadline_timer>(ios); 行——这是多余的吗?

【问题讨论】:

    标签: c++ boost parallel-processing wait boost-msm


    【解决方案1】:

    这是一个完整的代码示例:

    // 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。

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

        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()

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

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

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

    Sm 有成员变量 ios 来设置截止时间计时器。 MSM 要求状态机的前端是可复制的。所以ios是io_service的指针而不是引用。

    状态 A 和 B 是正交区域。为了实现正交区域,将多个初始状态定义为 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 invokesActSetParent. In the action, SourceState isState1_`。我将父成员变量设置为父指针,如下所示:

        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的action中调用process()了。

            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 一起使用的 pthread 会是一个更通用/更少约束的实现?

    Boost.MSM 的process_event() 不是线程安全的。所以你需要锁定它。见Thread safety in Boost msm AFAIK,sleep()/usleep()/nanosleep() 是阻塞函数。当您在 Boost.MSM 的操作中调用它们时,这意味着它们是从 process_event() 调用的(原始的)。它需要锁定。最后,阻塞等待会互相阻塞(在本例中为 after2 和 after5)。因此我认为 Boost.ASIO 的异步方法更好。

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

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

        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() 使用与&amp;&amp; 相同的模板参数。它们被称为转发引用(universal-reference)。这是一个成语,称为完美转发。 见http://en.cppreference.com/w/cpp/utility/forward

    1. 与 2 一起,更好地解释 Sm 的 wp 和 ios 成员变量的用途会很好。使用 ios 指针故意满足复制构造函数是什么意思?此外,除了在构造函数 Sm(boost::asio::io_service* ios) : ios(ios) {} 中,我没有看到 ios 被设置在任何地方,你似乎从未调用过它?

    到目前为止,Boost.MSM 不支持转发引用。我写了一个 pull request 见https://github.com/boostorg/msm/pull/8

    所以 forwarding-reference 调用 Boost.MSM 中的复制构造函数。这就是我选择 boost::asio::io_service 指针的原因。但是,这不是原始问题的要点。如果我不使用 forwarding-reference,我可以使用 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;
        }
    

    std::ref 不适用于 make_shared。它适用于 Boost.MSM。由于缺少转发引用支持,Boost.MSM 的构造函数是否需要指定引用。

    1. 在 State1_ 前端中,您在三个 on_entry 方法中有三个 BOOST_STATIC_ASSERT 调用。他们在做什么?

    它在运行时什么也不做。只是在编译时检查 Fsm 的类型。有时我对 Fsm 的类型感到困惑。我想读者也可能会感到困惑,所以我把它留在代码中。

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

    啊哈,我忘了擦掉它。我更新了代码。

    这是更新后的代码:

    #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();
    }
    

    【讨论】:

    • 非常感谢您的广泛回答!我在帖子末尾添加并编辑了有关您的代码的问题 - 您能否用这些问题的答案扩展您的帖子?再次感谢您的努力。
    • 非常感谢。同时,我试图为您的答案创建一个替代方案(使用我从您的答案中学到的东西)来与std::thread 进行并行行为。不过,我遇到了一堵墙,我发布了一个关于 here 的问题。如果您有时间回答这个问题,那就太好了,因为您对 MSM 非常了解。谢谢!
    • 我回答了您发布的新问题。顺便说一句,如果您满意我的回答,请检查复选标记。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-22
    • 2018-11-17
    相关资源
    最近更新 更多