通过延迟自转换来提升MSM并行行为?

spa*_*ger 1 c++ parallel-processing boost wait boost-msm

我正在使用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的pthreads会是一个更通用/更少约束的实现吗?
  2. 我不清楚createprocess方法是如何工作的(为什么create函数需要一个可变参数模板?).特别是,我之前没有使用过智能指针std::forward,所以如果你能对这些函数中的每一行给出一个人工解释,那就太棒了(我很难及时阅读这些特性,以便尝试理解这段代码).
  3. 与2一起,更好地解释wpios成员变量的目的Sm将是伟大的.使用ios指针故意满足复制构造函数是什么意思?我还没有看到ios被设置在任何地方,但在构造函数中Sm(boost::asio::io_service* ios) : ios(ios) {},似乎你从来没有打电话?
  4. State1_前端内部,您可以BOOST_STATIC_ASSERT在三种on_entry方法中进行三次调用.这些是做什么的?
  5. main()函数中,我能够删除行auto t = std::make_shared<boost::asio::deadline_timer>(ios);而不改变行为 - 它是多余的吗?

Tak*_*ndo 10

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

// 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();
}
Run Code Online (Sandbox Code Playgroud)

我们来挖掘代码.

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;
    }
Run Code Online (Sandbox Code Playgroud)

它创建后端的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));
    }
Run Code Online (Sandbox Code Playgroud)

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

    auto sm = Sm::create(&ios);
Run Code Online (Sandbox Code Playgroud)

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

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

    typedef mpl::vector<A, B> initial_state;
Run Code Online (Sandbox Code Playgroud)

状态A和B是复合状态.MSM使用子机状态来实现复合状态.外部大多数状态Sm是状态机,State1_也是状态机.我在状态A和B的输入操作中设置了一个计时器.当计时器被触发时,调用process().但是,processs()是一个成员函数Sm,而不是State1_.所以,我需要实现一些机制来访问SmStete1_.我加入成员变量parentState1_.这是一个指针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.
        }
    };
Run Code Online (Sandbox Code Playgroud)

最后,我可以打电话给process()A和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());
                    }
                );
            }
        };
Run Code Online (Sandbox Code Playgroud)

编辑

  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的行动中调用它们时,这意味着它们被称为(ogirinally)process_event().它需要锁定.最后,阻塞等待块(在这种情况下,after2和after5).因此我认为Boost.ASIO的异步approch更好.

  1. 我不清楚创建和处理方法是如何工作的(为什么create function需要一个可变参数模板?).特别是,我以前没有使用过智能指针或std :: forward,所以如果你能对这些函数中的每一行给出一个人工解释,那就太棒了(我很难及时阅读这些特性)为了试图理解这段代码).

Boost.MSM的后端继承了它的前端.前端构造函数是Sm(boost::asio::io_service* ios):ios(ios) {}.在这种情况下,构造函数的参数是ios.但是,它可以根据用例改变.该函数create()创建一个shared_ptr back.并且back构造函数将所有参数转发到前端.因此,ios的参数auto sm = Sm::create(&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;
    }
Run Code Online (Sandbox Code Playgroud)

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

  1. 与2一起,更好地解释Sm的wp和ios成员变量的目的将是很好的.使用ios指针故意满足复制构造函数是什么意思?我还没有看到ios被设置在任何地方,但在构造函数Sm(boost :: asio :: io_service*ios):ios(ios){},它似乎你从不打电话?

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

因此,forwarding-reference在Boost.MSM中调用copy-constructor.这就是我选择boost :: asio :: io_service指针的原因.但是,这不是原始问题的重点.如果我不使用转发引用,我可以使用引用类型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;
    }
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)