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

Boost MSM parallel behavior with delayed self-transitions?

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

换言之:

  1. 进入状态State1
  2. 进入状态A,执行action_A。 2 秒后,打印 "Trying again..." 并重新执行状态 A(即调用其进入动作)。这将永远循环...
  3. 与2同时(即"in parallel")进入状态B,执行action_B。 5 秒后,打印 "Trying again..." 并重新执行状态 B(即调用其进入动作)。这将永远循环...

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

非常感谢您的帮助。

编辑

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

  1. 这与 pthreads 实现相比如何?你认为 Boost.Asio 比将状态 A 和 B 放入不同的线程并在每个线程中进行阻塞、被动等待(例如通过 usleep(useconds_t usec)unistd.h 可以实现的)更好的解决方案吗? ?我的感觉是,我没有尝试将其与 Boost.MSM 一起使用的 pthreads 会是一个更 generic/less 受限的实现吗?
  2. 我不清楚 createprocess 方法是如何工作的(为什么 create 函数需要可变参数模板?)。特别是,我以前没有使用过智能指针或 std::forward,所以如果你能对这些函数中的每一行给出一个人性化的解释,那就太好了(我没有时间阅读这些功能的一般信息为了尝试理解这段代码)。
  3. 结合 2,更好地解释 wpios 成员变量的目的 Sm 会很棒。使用 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); - 它是多余的吗?

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

// 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 用来设置deadline timer。 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。 我在State1_中添加了成员​​变量parent。它是 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的动作中调用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. 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?

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

  1. 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).

Boost.MSM的后端继承了它的前端。前端构造函数是 Sm(boost::asio::io_service* ios):ios(ios) {}。在这种情况下,构造函数的参数是ios。但是,它可以根据用例进行更改。函数 create() 创建一个 shared_ptr of backback 的构造函数将所有参数转发给前端。因此 auto sm = Sm::create(&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()使用了&&的模板参数。它们被称为转发参考(universal-reference)。这是一个叫做完美转发的成语。 参见 http://en.cppreference.com/w/cpp/utility/forward

  1. 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?
到目前为止,

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

因此转发引用调用 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. Inside the State1_ front-end, you have three BOOST_STATIC_ASSERT calls in the three on_entry methods. What are these doing?

它在 运行 时间内什么都不做。只是在编译时检查 Fsm 的类型。有时我对 Fsm 的类型感到困惑。估计各位读者也可能会一头雾水,所以就放在代码里了。

  1. In the main() function, I was able to delete the line auto t = std::make_shared(ios); without changing the behaviour - was it redundant?

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

这是更新后的代码:

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