通过延迟的自我转换来增强MSM并行行为? [英] Boost MSM parallel behavior with delayed self-transitions?
问题描述
我正在使用Boost MSM(基本和函子前端),并试图实现以下状态机:
I am using Boost MSM (basic and functor front-ends) and am trying to implement the following state machine:
换句话说:
- 进入状态State1
- 进入状态A并执行action_A. 2秒钟后,打印正在重试..."并重新执行状态A(即调用其输入操作).这会永远循环下去...
- 与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.
- 这与pthreads实现相比如何?您是否认为Boost.Asio比将状态A和B置于不同的线程中并在每个线程中具有阻塞的被动等待(例如通过
unistd.h
的usleep(useconds_t usec)
可以实现的)更好的解决方案?我的感觉是,我没有尝试将其与Boost.MSM一起使用的pthreads将是一种更通用/更少约束的实现? - 我不清楚
create
和process
方法的工作方式(为什么create
函数需要可变参数模板?).特别是,我以前没有使用过智能指针或std::forward
,因此,如果您可以对这些函数中的每一行进行人为的解释,那就太好了(我很不方便按顺序阅读这些功能,尝试理解此代码). - 在第2章中,对
Sm
的wp
和ios
成员变量的用途的更好的解释将是很好的.使用ios
指针有意满足副本构造函数的含义是什么?此外,我看不到在构造函数Sm(boost::asio::io_service* ios) : ios(ios) {}
中的任何地方设置ios
,这似乎从未调用过? - 在
State1_
前端内部,在三个on_entry
方法中有三个BOOST_STATIC_ASSERT
调用.这些在做什么? - 在
main()
函数中,我能够删除auto t = std::make_shared<boost::asio::deadline_timer>(ios);
行而无需更改行为-多余吗?
- 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)
ofunistd.h
)? My feeling is that pthreads, which I have not tried using with Boost.MSM, would be a more generic/less constrained implementation? - I am not clear on how the
create
andprocess
methods work (why does thecreate
function need a variadic template?). In particular, I've not previously worked with smart pointers orstd::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). - In hand with 2, a better explanation of the purpose of the
wp
andios
member variables ofSm
would be great. What do you mean by usingios
pointer to intentionally meet copy constructor? I furthermore do not seeios
being set anywhere but in the constructorSm(boost::asio::io_service* ios) : ios(ios) {}
, which it seems that you never call? - Inside the
State1_
front-end, you have threeBOOST_STATIC_ASSERT
calls in the threeon_entry
methods. What are these doing? - In the
main()
function, I was able to delete the lineauto 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 invokes
ActSetParent. In the action, SourceState is
State1_`. 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());
}
);
}
};
修改
- 这与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.
- 我不清楚创建和处理方法的工作方式(为什么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
- 在第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.
- 在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.
- 在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屋!