状态机实现中的Type_index使用、销毁和同步
Type_index use, destruction, and synchronization in the state machine implementation
在一个正在进行的项目中,需要实现多个有限状态机。一个通用层会很好,这样可以开发类似的制衡机制。状态机的要求足够多样化,所以我希望在FSM模板之外确定数据类型,作为用户的责任。此外,我希望持久数据完全由FSM实现作者控制。最后,线程安全是模板中状态转换的必要条件。
这导致了我认为是一个精益状态转换代码。其思想是FSM模板应该由FSM实现者专门化和封装到可用的外部类中。该包装器类将是一个完全特定的FSM实现。FSM模板使用在其外部定义的数据类型和逻辑,但在FSM的最终包装器实现之外,这些状态和数据不应可见。
还有一些问题(按照C++标准,我是非常环保的)。它们在这里:
- 在这种情况下,使用
type_index
作为映射键是一种奇怪的做法,还是符合type_index
的设计意图?它足够独特吗 shared_ptr
的"魔力"会自动正确地销毁包装类中实例化的所有数据,这是对的吗- 此模板和实现中是否存在拆卸订单问题
- 有没有一种方法可以测试正确的破坏和它的顺序
- 是否有一种方法(或合理的需求)来测试
FSM::operator()
方法中的状态转换是否真正同步
下面是模板本身和主要的Turnstile类,从维基百科的文章中艰难地实现了这一点。如果需要的话,其他六个具有特定类型和状态逻辑的实现文件在我的GitHub FSM存储库中。
下面的FSM模板采用对消息作出反应的逻辑向量,向量的头部定义初始状态。它将每个消息参与者封装在一个状态对象中,并通过管道将状态数据附加到其中。当FSM模板运行一个状态时,它还可以进行清理检查,以确保返回的"下一个"状态密钥是有效的(future,此处为简洁起见省略)。
这是模板:
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <map>
#include <typeindex>
#include <mutex>
namespace tools {
template <typename Message, typename Content>
class FSM {
public:
class React {
public:
virtual std::type_index operator()( const Message & msg, std::shared_ptr<Content> ) {
return std::type_index(typeid(this));
};
};
typedef std::vector<std::shared_ptr<React>> Flux;
FSM( Flux & flux, std::shared_ptr<Content> ctnt )
: current( std::type_index( typeid( *( flux.at(0) ) ) ) )
, content( ctnt )
{
for ( auto rct: flux ) {
states[ std::type_index( typeid( *rct ) ) ] = State( rct, content );
};
};
void operator()( const Message & msg ) {
std::lock_guard<std::mutex> lock( fsm_mutex );
current = states.at( current )( msg );
};
private:
class State {
public:
State( std::shared_ptr<React> rct, std::shared_ptr<Content> ctnt )
: content( ctnt ), react( rct ) {};
State() : content( nullptr ), react( nullptr ) {};
std::type_index operator()( const Message & message ) {
return (*react)( message, content );
}
private:
std::shared_ptr<Content> content;
std::shared_ptr<React> react;
};
std::mutex fsm_mutex;
std::shared_ptr<Content> content;
std::type_index current;
std::map<std::type_index, State> states;
};
}
实现类标题:
#pragma once
#include "TurnstileTypes.h"
namespace tools {
namespace test {
namespace fsm {
// Implementation of the Turnstile FSM example from http://en.wikipedia.org/wiki/Finite_state_machine#Example:_a_turnstile
// Added RESET event to better test state.
class Turnstile {
public:
Turnstile(void);
void operator()( const TurnstileEvent & msg );
unsigned long Pushed();
unsigned long Paid();
std::string Now();
private:
TurnstileFSM::Flux flux;
std::unique_ptr<TurnstileFSM> fsm;
std::shared_ptr<TurnstileData> stats;
};
}
}
}
实现类主体:
#include "TurnstileTypes.h"
#include "TurnstileLocked.h"
#include "TurnstileUnlocked.h"
#include "Turnstile.h"
namespace tools {
namespace test {
namespace fsm {
Turnstile::Turnstile(void)
: stats( std::make_shared<TurnstileData>() )
{
flux.push_back( std::make_shared<Locked>() );
flux.push_back( std::make_shared<Unlocked>() );
fsm = std::unique_ptr<TurnstileFSM>( new TurnstileFSM( flux, stats ) );
};
void Turnstile::operator()( const TurnstileEvent & msg ) {
(*fsm)( msg );
};
unsigned long Turnstile::Pushed() { return stats->pushes; };
unsigned long Turnstile::Paid() { return stats->coins; };
std::string Turnstile::Now() { return stats->state_name; };
}
}
}
非常感谢你的回答!
[update 131120]值得一提的是,FSM.h
并不意味着是一个具有"公共接口"的库。更重要的是,这是一个让FSM作者继续工作的样板代码。实际用户FSM接口由FSM实现定义——在所附示例中,它位于Turnstile.h
中。因此,FSM.h模板非常简短,旨在供FSM作者阅读和理解。
我还希望他们(目前我自己)理解模板的缺陷,比如在State()()
方法中向任何FSM发送消息都会被死锁。这可能是由一连串的呼叫引起的。感谢Florian Philipp在Google+上的评论指出了这一点。
还有一个目的是在FSM中对Message
和Content
类型了解最少,这样FSM作者在实现时就有了最大的灵活性,包括通过访问器之类的东西从内容中获取信息。这种灵活性伴随着不更改内容的责任。
顺便说一句,如果你在编译时了解你的状态机,那么像Maxim提到的那样使用Boost.MSM可能会更容易。
现在回答您的问题。
-
我相信type_index是为与map和undered_map一起使用而设计的,所以您可以按设计使用它。
-
shared_ptr将正确地进行清理,但是,请考虑在此处避免它。看起来真的没有共同所有权。对于内容,决定FSM或用户代码是否拥有它,并对FSM::content使用unique_ptr或原始ptr/reference,其余可以是原始ptr/refs。我也会让React归FSM所有。typedef通量为向量<unique_ptr<React>gt;并将所有权移动到状态对象中。
-
销毁命令在这里应该没问题。
-
我不知道有什么简单的方法可以测试销毁顺序的正确性,但你所有的销毁程序都很琐碎,所以顺序并不重要。
-
您的公共接口由一个通过互斥对象序列化的方法组成——实际上不需要测试它是否会同步。如果用户触摸React子类化对象,也可能会导致问题,但是,如果您将它们移动(而不是共享它们)到FSM中,这就不是问题。此外,请不要从FSM内外读取/更新内容,这可能会导致同步问题。
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- 如何使用一个信号灯同步 3 个进程?
- 如何在没有同步的情况下使用多个线程(2、4,8、16 个线程)在循环(10,100、1000 个周期)中打印字符串?
- 单车道桥 使用信号量进行同步
- 使用 Visual c++ 进行多线程同步不起作用
- 使用 P/调用传递取消标志时是否需要同步
- 在 C Linux 中使用三个线程使用信号量同步按顺序打印 3 4 5 50 次
- 如何使用适用于 Mac 和 Windows 10 的 vscode 设置同步插件配置C++智能感知模式
- 使用boost :: Beast进行CPU重的REST API,我是否应该使用异步或同步方式来实现它们以期望延迟
- 使用 qt 和 opengl、定时精度和垂直同步问题、c++ 显示图像
- 使用C++同步控制多个步进器
- 生产者消费者使用PTHreads和Semaphore的同步错误
- 释放在不同同步上下文中使用的类成员
- 如果我确定只有一个线程一次处理指针/对象,则C/C 仍应使用同步
- 线程安全关闭同步使用的 boost::asio::ip::tcp::socket
- C++:Linux 中的计时(使用 clock())不同步(由于 OpenMP?)
- 通过使用覆盖操作员的代理来同步对象
- 在Windows(win32或C++)中使用互斥锁的进程间同步
- 使用条件变量(监视器)同步线程
- POSIX 线程 - 使用条件变量 MEMORY LEAK 同步分离的线程