1

我正在使用 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)of实现什么unistd.h)更好的解决方案?我的感觉是,我没有尝试与 Boost.MSM 一起使用的 pthread 将是一个更通用/更少约束的实现?
  2. 我不清楚createandprocess方法是如何工作的(为什么create函数需要可变参数模板?)。std::forward特别是,我以前没有使用过智能指针或理解这段代码)。
  3. 与 2 一起,更好地解释 thewpios成员变量的用途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);不改变行为的情况下删除该行 - 它是多余的吗?
4

1 回答 1

12

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

// 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_。所以我需要实现一些机制来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.
        }
    };

最后,我可以调用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());
                    }
                );
            }
        };

编辑

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

Boost.MSMprocess_event()不是线程安全的。所以你需要锁定它。请参阅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()创建一个 shared_ptr back。Andback的构造函数将所有参数转发到前端。所以参数 ios atauto 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;
    }

此外,create()process()使用带有&&. 它们被称为转发引用(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 不支持转发引用。我写了一个拉请求见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;
    }

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();
}
于 2017-03-21T13:38:38.967 回答