如果要在C ++项目中使用Actor或CSP,只需看一下SObjectizer


关于SObjectizer及其历史的几句话


SObjectizer是一个相当小的C ++框架,它简化了多线程应用程序的开发。 SObjectizer允许开发人员使用Actor,Publish-Subscribe和Communicating Sequential Processes(CSP)模型中的方法。 这是一个开放源代码项目,已获得BSD-3-CLAUSE许可。


SObjectizer历史悠久。 SObjectizer本身诞生于2002年,当时是SObjectizer-4项目。 但这是基于1995年至2000年开发的先前SCADA Objectizer的思想。SObjectizer-4于2006年开源,但此后不久就停止了其发展。 新版本的SObjectizer(名称为SObjectizer-5)于2010年启动,并于2013年开源。SObjectizer-5的演进仍在进行中,SObjectizer-5自2013年以来已合并了许多新功能。


SObjectizer在Internet的俄罗斯部分或多或少是众所周知的,但是在exUSSR之外几乎是未知的。 这是因为SObjectizer主要用于exUSSR国家的本地项目,许多关于SObjectizer的文章,演示文稿和讨论都是俄语的。


SObjectizer和类似工具的利基市场


多线程用于并行计算以及并行计算 。 但是并行计算和并行计算之间有很大的区别。 结果,存在针对并行计算的工具,以及针对并行计算的工具,它们是不同的。


粗略地说,并行计算就是要使用多个内核来减少计算时间。 例如,在一个CPU内核上将视频文件从一种格式转码为另一种格式可能需要一个小时,而在四个CPU内核上则只需15分钟。 诸如OpenMP,Intel TBB,HPX或cpp-taskflow之类的工具旨在用于并行计算。 这些工具支持适合该领域方法的工具,例如基于任务的编程或数据流编程。


并行计算是关于同时处理许多(可能不同)的任务。 数据库服务器或MQ-broker就是很好的例子:服务器必须接受连接,从接受的连接读取和解析数据,处理接收到的请求(对每个请求执行多个操作),发送响应等等。 严格来说,并发计算中不需要使用多线程:所有这些任务都可以在一个工作线程上执行。 但是使用多线程和几个CPU内核可以使您的应用程序更具性能,可伸缩性和响应能力。


Actor Model或CSP之类的方法旨在处理并发计算。 并发计算领域中的Actor用法很好的例子是InfineSQL项目Yandex Message-Queue 。 这两个项目都在内部使用actor。


因此,支持Actor模型的SObjectizer,QP / C ++或CAF之类的工具对于解决并行计算领域的任务很有用。 这意味着在诸如视频流转换之类的任务中,使用SObjectizer可能不会给您任何好处。 但是您可以在SObjectizer之上实现消息代理得到一个截然不同的结果。


免责声明


使用Actor或CSP模型可以在某些任务中为您带来巨大的好处,但不能保证这些模型适合您的特定问题。 关于Actor或CSP模型的适用性的讨论不在该文章的讨论范围之内。 假定Actor或/和CSP模型适用于您的任务,并且您知道如何有效地使用它们。


SObjectizer可以给用户什么?


开箱即用的无共享原则


Actor的使用假定不存在任何共享数据。 每个参与者都拥有其数据,并且其他任何人都看不到该数据。 例如,这是分布式应用程序开发中众所周知的无共享原则 。 在多线程应用程序中,无共享原则有一个重要的好处:它可以避免使用共享数据时发生死锁和数据争用之类的危险问题。


SObjectizer中的参与者(代理)之间的交互仅通过异步消息执行。 一个代理向另一个代理发送一条消息,并且此操作不会阻止发件人(在通常情况下)。


异步交互允许使用另一个有用的原理: 即发即弃 。 当某些代理需要完成某些操作时,它将发送(触发)消息并继续其工作。 在大多数情况下,将接收并处理该消息。


例如,可能有一个代理读取接受的连接并解析传入的数据。 如果读取并解析了整个PDU,则代理程序仅将该PDU发送给另一个代理程序处理器,然后返回读取/解析新的传入数据。


调度员


调度程序是SObjectizer的基石之一。 调度程序提供了一个工作上下文(也称为工作线程),代理可以处理传入的消息。 用户无需手动创建工作线程(或线程池),而是创建调度程序并将代理绑定到它们。 用户可以根据需要在应用程序中创建任意数量的调度程序。


SObjectizer中的调度程序和代理程序最好的事情是概念的分离:调度程序负责管理工作上下文和自己的消息队列,代理程序执行应用程序逻辑,而不必担心工作程序上下文。 它允许通过单击从字面上将代理从一个调度员移动到另一个调度员。 昨天,一个代理在one_thread调度程序上工作,今天我们可以将其重新绑定到active_obj调度程序,明天我们可以将其重新绑定到thread_pool调度程序。 无需在代理的实现中更改任何一行。


SObjectizer-5.6.0中八种调度程序另外一种可以在so5extra伴随项目中找到):从非常简单的调度程序(one_thread或thread_pool)到复杂的调度程序(如adv_thread_pool或prio_dedicated_threads :: one_per_prio)。 用户可以针对特定条件编写自己的调度程序。


分层状态机是内置功能


SObjectizer中的代理(角色)是状态机:对传入消息的反应取决于代理的当前状态。 SObjectizer支持大多数分层状态机(HSM)功能:嵌套状态,状态的深浅历史记录,on_enter / on_exit处理程序,保持状态的时间限制。 现在,SObjectizer中仅不支持正交状态(我们没有在项目中看到该功能的必要性,也没有人要求我们添加对该功能的支持)。


类似于CSP的渠道


无需使用SObjectizer的代理(也称为actor)。 只需使用std::thread对象和SObjectizer的mchains(aka CSP-channels)即可开发整个应用程序。 在这种情况下,使用SObjectizer进行的应用程序开发将类似于Go语言的开发(包括Go语言的select结构的类似物,该结构允许等待来自多个通道的消息)。


SObjectizer的mchain可以具有非常重要的功能:合并了反压机制。 如果用户创建了大小受限制的mchain,然后尝试将消息推送到完整的mchain,则send操作可能会阻止发件人一段时间。 它可以解决快速生产者和缓慢消费者的著名问题。


SObjectizer的mchain具有另一个有趣的功能:mchain可以用作非常简单的负载分配工具。 多个线程可以同时等待来自同一mchain的接收 。 如果将新消息发送到该mchain,则只有一个线程将读取并处理该消息。


应用程序只有一部分可以使用SObjectizer


无需在应用程序的每个部分中使用SObjectizer。 使用SObjectizer可以开发应用程序的一部分。 因此,如果您已经使用Qt或wxWidgets或Boost.Asio作为应用程序的主要框架,则可以仅在应用程序的一个子模块中使用SObjectize。


我们具有使用SObjectizer进行库开发的经验,这些库将SObjectizer的用法隐藏为实现细节。 这些库的公共API根本没有公开SObjectizer的存在。 SObjectizer完全在一个库的控制之下:该库根据需要启动和停止SObjectizer。 这些库用于完全不知道SObjectizer存在的应用程序中。


如果仅在应用程序的一部分中使用SObjectizer,则需要在应用程序的SObjectizer部件和非SObjectizer部件之间进行通信。 这个任务很容易解决:从非SObjectizer部分到SObjectizer部分的消息可以通过普通的消息传递SObjectizer机制发送。 相反方向的消息可以通过mchain传递。


您可以同时运行多个SObjectizer实例


SObjectizer允许在一个应用程序中同时运行多个SObjectizer实例(称为SObjectizer Environment)。 每个SObjectizer环境将独立于其他此类环境。


在必须从多个独立模块构建应用程序的情况下,此功能非常有用。 有些模块可以使用SObjectizer,有些则不能。 那些需要SObjectizer的模块可以运行其SObjectizer Environment的副本,并且不会对应用程序中的其他模块产生影响。


计时器是SObjectizer的一部分


支持延迟和周期性消息形式的计时器是SObjectizer的另一个基础。 SObjectizer具有计时器机制的几种实现(timer_wheel,timer_heap和timer_list),并且可以处理应用程序中的数以千万计的计时器。 用户可以为应用程序选择最合适的计时器机制。 此外,如果没有标准的标准程序适合用户的情况,则用户可以提供自己的timer_thread / timer_manager实现。


SObjectizer具有各种自定义点和调整选项


SObjectizer允许自定义几种重要的机制。 例如,用户可以选择timer_thread(或timer_manager)的标准实现之一。 或者可以提供自己的实现。 用户可以选择SObjectizer的分派器中消息队列使用的锁定对象的实现。 或者可以提供自己的实现。


用户可以实现自己的调度程序。 用户可以实现自己的消息框。 用户可以实现自己的消息信封。 用户可以实现自己的event_queue_hook。 依此类推。


在哪里可以使用SObjectizer?


说出出于客观原因不能使用SObjectizer的地方要容易得多。 因此,我们通过列举这些领域来开始讨论,然后再给出一些过去(不仅是过去)使用SObjectizer的示例。


无法在哪里使用SObjectizer?



如前所述,Actor和CSP模型对于高性能计算和并行计算的其他领域不是一个好的选择。 因此,如果您必须使用多个矩阵或对视频流进行转码,那么像OpenMP,Intel TBB,cpp-taskflow,HPX或MPI之类的工具将更适合。


硬实时系统


尽管SObjectizer根植于SCADA系统,但当前的SObjectizer实现(又名SObjectizer-5)不能在硬实时系统中使用。 这主要是由于在SObjectizer实现中使用了动态内存:消息是动态分配的对象(但是,SObjectizer可以将预分配的对象用作消息),调度程序将动态内存用于消息队列,甚至代理状态的时限也使用动态分配的对象执行时间检查。


不幸的是,术语“实时”在现代世界中被过度使用。 人们经常谈论实时Web服务,例如“实时Web应用程序”或“实时Web分析”等。 术语“在线”或“实时”比术语“实时”更适合于此类应用程序,即使是“软实时”形式也是如此。 因此,如果我们谈论诸如“实时Web应用程序”之类的东西,那么SObjectizer可以轻松地用于此类“实时”系统中。


受限的嵌入式系统


SObjectizer依赖于C ++标准库: std::thread用于线程管理, std::atomicstd::mutexstd::condition_variable用于数据同步,RTTI和dynamic_cast用于insize SObjectizer(例如, std::type_index用于消息类型识别),C ++异常用于错误报告。


这意味着SObjectizer不能在标准库的此类功能不可用的环境中使用。 例如,在受限嵌入式系统的开发中,只能使用C ++和C ++ stdlib的一部分。


过去在哪里使用过SObjectizer?


现在,我们尝试简要地谈论一下过去(不仅是过去)使用SObjectizer的一些用例。 不幸的是,由于某些问题,它不是完整的信息。


首先,我们不了解SObjectizer的所有用法。 SObjectizer是免费软件,即使在专有项目中也可以使用。 因此,有些人只是获得SObjectizer并使用它而没有为我们提供任何反馈。 有时我们获得有关SObjectizer用法的一些信息(但没有任何细节),有时我们一无所知。


第二个问题是在特定项目中共享有关SObjectizer用法的信息的权限。 我们很少收到该许可,在大多数情况下,SObjectizer的用户都不想打开其项目的实现详细信息(有时我们了解原因,有时却不了解)。


对于所提供的信息非常稀缺且不包含任何详细信息,我们深表歉意。 尽管如此,仍有一些使用SObjectizer的示例:


  • SMS / USSD聚合网关,每月处理超过500M消息;
  • 通过最大的俄罗斯银行之一的ATM机进行在线支付的系统的一部分;
  • 经济过程的模拟建模(作为博士学位研究的一部分);
  • 分布式数据采集与分析系统。 通过中心节点的命令在全球分布的点上收集的数据。 MQTT用作控制和获取数据分发的传输;
  • 检验铁路设备实时控制系统的测试环境;
  • 剧院风光自动控制系统。 更多细节可以在这里找到;
  • 在线广告系统中数据管理平台的组件。

SObjectizer的味道


让我们看几个简单的例子,以品尝一下SObjectizer。 这些是非常简单的示例,我们希望它们不需要除代码中的注释之外的其他解释。


Actor Model风格的传统“ Hello,World”示例


最简单的示例,其中只有一个代理对hello消息做出反应并完成其工作:


 #include <so_5/all.hpp> // Message to be sent to an agent. struct hello { std::string greeting_; }; // Demo agent. class demo final : public so_5::agent_t { void on_hello(mhood_t<hello> cmd) { std::cout << "Greeting received: " << cmd->greeting_ << std::endl; // Now agent can finish its work. so_deregister_agent_coop_normally(); } public: // There is no need is a separate constructor. using so_5::agent_t::agent_t; // Preparation of agent to work inside SObjectizer. void so_define_agent() override { // Subscription to 'hello' message. so_subscribe_self().event(&demo::on_hello); } }; int main() { // Run SObjectizer instance. so_5::launch([](so_5::environment_t & env) { // Make and register an instance of demo agent. auto mbox = env.introduce_coop([](so_5::coop_t & coop) { auto * a = coop.make_agent<demo>(); return a->so_direct_mbox(); }); // Send hello message to registered agent. so_5::send<hello>(mbox, "Hello, World!"); }); } 

带有代理和发布/订阅模型的“ Hello,World”的另一个版本


最简单的示例,其中包含多个代理,它们都对同一hello消息实例做出反应:


 #include <so_5/all.hpp> using namespace std::string_literals; // Message to be sent to an agent. struct hello { std::string greeting_; }; // Demo agent. class demo final : public so_5::agent_t { const std::string name_; void on_hello(mhood_t<hello> cmd) { std::cout << name_ << ": greeting received: " << cmd->greeting_ << std::endl; // Now agent can finish its work. so_deregister_agent_coop_normally(); } public: demo(context_t ctx, std::string name, so_5::mbox_t board) : agent_t{std::move(ctx)} , name_{std::move(name)} { // Create a subscription for hello message from board. so_subscribe(board).event(&demo::on_hello); } }; int main() { // Run SObjectizer instance. so_5::launch([](so_5::environment_t & env) { // Mbox to be used for speading hello message. auto board = env.create_mbox(); // Create several agents in separate coops. for(const auto & n : {"Alice"s, "Bob"s, "Mike"s}) env.register_agent_as_coop(env.make_agent<demo>(n, board)); // Spread hello message to all subscribers. so_5::send<hello>(board, "Hello, World!"); }); } 

如果运行该示例,我们将收到类似的内容:


 Alice: greeting received: Hello, World! Bob: greeting received: Hello, World! Mike: greeting received: Hello, World! 

CSP风格的“ Hello,World”示例


让我们看一个没有任何参与者的SObjectizer示例,只是std::thread和类似CSP的通道。


非常简单的版本


这是一个非常简单的版本,并非异常安全:


 #include <so_5/all.hpp> // Message to be sent to a channel. struct hello { std::string greeting_; }; void demo_thread_func(so_5::mchain_t ch) { // Wait while hello received. so_5::receive(so_5::from(ch).handle_n(1), [](so_5::mhood_t<hello> cmd) { std::cout << "Greeting received: " << cmd->greeting_ << std::endl; }); } int main() { // Run SObjectizer in a separate thread. so_5::wrapped_env_t sobj; // Channel to be used. auto ch = so_5::create_mchain(sobj); std::thread demo_thread{demo_thread_func, ch}; // Send a greeting. so_5::send<hello>(ch, "Hello, World!"); // Wait for demo thread. demo_thread.join(); } 

更强大,但仍然简单的版本


这是上面显示的示例的修改版本,其中增加了异常安全性:


 #include <so_5/all.hpp> // Message to be sent to a channel. struct hello { std::string greeting_; }; void demo_thread_func(so_5::mchain_t ch) { // Wait while hello received. so_5::receive(so_5::from(ch).handle_n(1), [](so_5::mhood_t<hello> cmd) { std::cout << "Greeting received: " << cmd->greeting_ << std::endl; }); } int main() { // Run SObjectizer in a separate thread. so_5::wrapped_env_t sobj; // Demo thread. We need object now, but thread will be started later. std::thread demo_thread; // Auto-joiner for the demo thread. auto demo_joiner = so_5::auto_join(demo_thread); // Channel to be used. This channel will be automatically closed // in the case of an exception. so_5::mchain_master_handle_t ch_handle{ so_5::create_mchain(sobj), so_5::mchain_props::close_mode_t::retain_content }; // Now we can run demo thread. demo_thread = std::thread{demo_thread_func, *ch_handle}; // Send a greeting. so_5::send<hello>(*ch_handle, "Hello, World!"); // There is no need to wait for something explicitly. } 

一个相当简单的HSM示例:blinking_led


这是来自SObjectizer发行版的标准示例。 此示例的主要代理是可以通过以下状态图描述的HSM:


blinking_led状态图


该示例的源代码:


 #include <iostream> #include <so_5/all.hpp> class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off final : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { auto m = env.introduce_coop( []( so_5::coop_t & coop ) { auto led = coop.make_agent< blinking_led >(); return led->so_direct_mbox(); } ); auto pause = []( unsigned int v ) { std::this_thread::sleep_for( std::chrono::seconds{v} ); }; std::cout << "Turn blinking on for 10s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); pause( 10 ); std::cout << "Turn blinking off for 5s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); pause( 5 ); std::cout << "Turn blinking on for 5s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); pause( 5 ); std::cout << "Stopping..." << std::endl; env.stop(); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } 

计时器,代理程序的重载控制和active_obj调度程序


过载控制是参与者的主要问题之一:参与者的消息队列通常是不受限制的,并且如果快速消息生成者更快地发送消息然后接收者可以处理它们,则这可能导致队列的不受控制的增长。 以下示例显示了SObjectizer的功能(如消息限制) 。 它允许限制代理队列中消息的数量,并保护接收者免受冗余消息的侵害。


此示例还以定期消息的形式显示了计时器的用法。 此处还显示了代理与active_obj调度程序的绑定。 绑定到该调度程序意味着该Coop的每个代理都将在自己的工作线程上工作(例如,代理成为活动对象)。


 #include <so_5/all.hpp> using namespace std::chrono_literals; // Message to be sent to the consumer. struct task { int task_id_; }; // An agent for utilization of unhandled tasks. class trash_can final : public so_5::agent_t { public: // There is no need is a separate constructor. using so_5::agent_t::agent_t; // Preparation of agent to work inside SObjectizer. void so_define_agent() override { // Subscription to 'task' message. // Event-handler is specified in the form of a lambda-function. so_subscribe_self().event([](mhood_t<task> cmd) { std::cout << "unhandled task: " << cmd->task_id_ << std::endl; }); } }; // The consumer of 'task' messages. class consumer final : public so_5::agent_t { public: // We need the constructor. consumer(context_t ctx, so_5::mbox_t trash_mbox) : so_5::agent_t{ctx + // Only three 'task' messages can wait in the queue. limit_then_redirect<task>(3, // All other messages will go to that mbox. [trash_mbox]{ return trash_mbox; })} { // Define a reaction to incoming 'task' message. so_subscribe_self().event([](mhood_t<task> cmd) { std::cout << "handling task: " << cmd->task_id_ << std::endl; std::this_thread::sleep_for(75ms); }); } }; // The producer of 'test' messages. class producer final : public so_5::agent_t { const so_5::mbox_t dest_; so_5::timer_id_t task_timer_; int id_counter_{}; // Type of periodic signal to produce new 'test' message. struct generate_next final : public so_5::signal_t {}; void on_next(mhood_t<generate_next>) { // Produce a new 'task' message. so_5::send<task>(dest_, id_counter_); ++id_counter_; // Should the work be stopped? if(id_counter_ >= 10) so_deregister_agent_coop_normally(); } public: producer(context_t ctx, so_5::mbox_t dest) : so_5::agent_t{std::move(ctx)} , dest_{std::move(dest)} {} void so_define_agent() override { so_subscribe_self().event(&producer::on_next); } // This method will be automatically called by SObjectizer // when agent starts its work inside SObjectizer Environment. void so_evt_start() override { // Initiate a periodic message with no initial delay // and repetition every 25ms. task_timer_ = so_5::send_periodic<generate_next>(*this, 0ms, 25ms); } }; int main() { // Run SObjectizer instance. so_5::launch([](so_5::environment_t & env) { // Make and register coop with agents. // All agents will be bound to active_obj dispatcher and will // work on separate threads. env.introduce_coop( so_5::disp::active_obj::make_dispatcher(env).binder(), [](so_5::coop_t & coop) { auto * trash = coop.make_agent<trash_can>(); auto * handler = coop.make_agent<consumer>(trash->so_direct_mbox()); coop.make_agent<producer>(handler->so_direct_mbox()); }); }); } 

如果运行该示例,我们将看到以下输出:


 handling task: 0 handling task: 1 unhandled task: 5 unhandled task: 6 handling task: 2 unhandled task: 8 unhandled task: 9 handling task: 3 handling task: 4 handling task: 7 

此输出显示拒绝了几个超出定义的限制的消息,并将其重定向到另一个接收者。


更多例子


在我们的Shrimp演示项目中可以找到一个与真实应用程序的代码大致相似的示例 。 在这个有关经典“用餐哲学家问题”的微型著作中,可以找到另一组有趣的例子: 第1 部分第2部分 。 而且,当然, SObjectizer本身有很多示例


表现如何呢?


有一个非常简单的答案:对我们来说这已经足够了。 SObjectizer可以每秒分发数百万条消息,而实际速度取决于所使用的调度程序的类型,消息类型,负载配置文件,所使用的硬件/ OS /编译器等。 在实际的应用程序中,我们通常仅使用SObjectizer速度的一小部分。


SObjectizer针对特定任务的性能在很大程度上取决于您的任务,该任务的特定解决方案,您的硬件或虚拟环境,您的编译器版本和操作系统。 因此,找到该问题答案的最好方法是创建特定于您的任务的基准并进行试验。


如果要从某些综合基准中获取数字,则SObjectizer发行版的test / so_5 / bench文件夹中有一些程序。


关于使用不同工具进行比较的注意事项


我们认为比较不同工具速度的基准测试游戏是营销游戏。 我们过去曾进行过尝试 ,但很快意识到这只是浪费时间。 因此,我们现在不玩该游戏。 我们仅将时间和资源用于基准测试,以检查是否存在性能下降的情况,以解决一些极端情况(例如具有大量订阅者的MPMC mbox的性能或具有数十万订阅的代理的性能),加快某些SObjectizer特定的操作(例如,协作室的注册/注销)。


因此,我们将速度的比较留给喜欢该游戏并有时间玩的人。


为什么SObjectizer看起来完全一样?


有几种C ++的“ actor框架”,它们看起来都不同。 似乎有一些客观原因:每个框架都有其独特的功能并针对不同的目标。 而且,C ++中的参与者可以以非常不同的方式实现。 因此,主要问题不是“为什么框架X看起来不像框架Y?”,而是“为什么框架X看起来像原样?”。


现在,我们将尝试简要描述SObjectizer主要功能背后的一些原因。 我们希望它可以更好地理解SObjectizer的功能。 但是,在我们开始之前,有必要提一个非常重要的事情:SObjectizer从来没有做过实验。 它是为解决现实生活中的工作而创建的,并且根据现实生活中的经验不断发展。


代理是从agent_t派生的类的对象


SObjectzer中的代理(即actor)是用户定义类的对象,这些类必须从特殊类agent_t 。 在很小的玩具示例中,它看起来似乎是多余的,但是我们的经验表明,这种方法极大地简化了实际软件的开发,在这种情况下,代理通常具有数百行的大小(您可以在此处看到示例之一,但是此博客文章位于俄语)。 有时甚至数千行。


经验表明,在接下来的几年发展中,具有一百行的第一个版本的简单代理会变得更加胖和复杂。 因此,五年后,您可以使用数十种方法在一千行中找到一个怪物。


使用类可以使我们管理代理的复杂性。 我们可以使用类的继承。 我们也可以使用模板类。 这些非常有用的技术可以大大简化内部具有相似逻辑的代理族的开发。


消息作为用户结构/类的对象


SObjectizer中的消息是用户定义的结构或类的对象。 至少有两个原因:


  • SObjectizer-5的开发始于2010年,当时C ++ 11还没有标准化。 因此,一开始,我们不能使用C ++ 11的这些功能(如可变参数模板和std::tuple类)。 我们唯一的选择是使用从特殊类message_t继承的类的对象。 现在,无需从message_t派生消息的类型,但是无论如何,SObjectizer都会将用户对象包装到message_t派生的对象中。
  • 无需修改事件处理程序的签名,即可轻松更改消息的内容。 并且有一个来自编译器的控件:如果您从消息中删除某些字段或更改其类型,则编译器会告诉您对该字段的错误访问。

将消息用作对象还可以使用预分配的消息,并将接收到的消息存储到某个容器中,然后稍后重新发送。


代理商合作社


座席合作社可能是SObjectizer的独特功能之一。 合作社是一组代理,应以事务方式将其添加到SObjectizer或从中删除。 这意味着,如果一个协作组包含三个代理,则应将所有这些代理成功添加到SObjectizer或不添加任何一个。 同样,应从SObjectizer中删除所有三个代理,或者所有三个代理应继续其工作。


在SObjectizer生命周期开始后不久就发现了合作社的需求。 很明显,代理将由组而不是单个实例创建。 发明了Coops是为了简化开发人员的生活:如果新代理创建失败,则无需控制下一个代理的创建并删除先前创建的代理。


合作社也可以看作是一对一模式的管理者:如果合作社中的代理失败,则整个合作社将从SObjectizer Environment中删除并销毁(用户可以对此做出反应并重新创建合作社)。


留言框


消息框是SObjectizer的另一个独特功能。 SObjectizer中的消息将发送到消息框(mbox),而不是直接发送到代理。 mbox后面可能有一个接收器,或者可能有一百万个订阅者,或者没有人。


Mboxes使我们能够支持Publish-Subscribe模型的基本功能。 mbox可以视为MQ-broker,消息类型可以视为主题。


Mboxes还使我们能够实现各种有趣的消息传递形式。 例如,有一个循环mbox ,它以循环方式在订户之间传播消息。 还有一个保留的mbox ,用于保存最后发送的消息,并为每个新订户自动重新发送。 在libmosquitto周围还有一个简单的包装器 ,可以将MQTT用作分布式应用程序的传输。


作为HSM的代理


SObjectizer中的代理是状态机。 它从一开始就是因为SObjectizer根植于SCADA领域,而SCADA领域则活跃地使用了状态机。 但是很快就很明显,状态机形式的代理即使在不同的领域(例如电信和金融应用程序)也可以使用。


在生产中使用SObjectizer一段时间后,添加了对分层状态机的支持(例如on_enter / on_exit处理程序,嵌套状态,时间限制等)。 而且,此功能使SObjectizer变得更强大,更方便。


C ++异常的用法


C ++异常在SObjectizer中用作主要的错误报告机制。 尽管事实上使用C ++异常有时可能会花费很大,但我们还是决定使用异常代替错误代码。


我们对SObjectizer-4中的错误代码有负面的经验,其中没有使用异常。 这导致对应用程序代码中的错误的无知,并且有时由于创建新的合作伙伴或发送消息时出错而未执行重要的操作。 但是这个错误被忽略了,后来发现了这个事实。


在SObjectizer-5中使用C ++异常允许编写更正确和更强大的代码。 在通常情况下,异常很少被SObjectizer抛出,因此异常的使用不会对SObjectizer的性能或在SObjectizer之上编写的应用程序的性能产生负面影响。


不支持“开箱即用”的分布式应用程序


SObjectzer-5不对分布式应用程序提供内置支持。 这意味着SObjectizer仅在一个进程内分发消息。 如果需要组织进程间或注释间消息分发,则必须在应用程序中集成某种IPC。


这不是因为我们无法在SObjectizer中实现某种形式的IPC。 我们已经在SObjectizer-4中有了它。 并且由于我们有这样的经验,所以我们决定不使用SObjectizer-5。 我们了解到,没有一种IPC能够完美地适应不同的条件。


如果要在应用程序中实现良好的节点间通信,则必须选择适当的基础协议。 例如,如果您必须散布数百万个带有一些短命数据的小数据包(例如当前天气状况的测量分布),则必须使用一个IPC。 但是,如果您必须传输大量的BLOB(例如4K / 8K视频流或内部包含财务数据的档案),则必须使用另一种IPC类型。


而且我们不会谈论使用不同语言编写的软件的不可逆性...


您可以相信,某些通用的“参与者框架”可以为您提供适用于不同条件的IPC。 但是我们知道这只是营销废话。 我们的经验告诉我们,在您的应用程序中添加所需的IPC,然后依靠第三方“参与者框架”作者的想法,需求和知识,要简单,安全得多。


SObjectizer允许以自定义mbox的形式合并各种类型的IPC。 因此,它允许向SObjectizer的用户隐藏通过网络分发消息的事实。


而不是结论


SObjectizer框架不是一个大框架,但也不是一个小框架。 因此,不可能仅凭一个概述就使读者对SObjectizer产生深刻的印象。 因此,我们邀请您看一下SObjectizer项目。


SObjectizer本身位于GitHub上GitHub上有该项目的Wiki,我们建议从SObjectizer 5.6 Basics开始,然后转到In-depth系列文章。 对于那些想更深入的人,我们可以建议让我们看一下SObjectizer的内幕部分。


如果您有任何疑问,可以在Google 网上论坛的SObjectizer网上论坛中问我们。

Source: https://habr.com/ru/post/zh-CN458202/


All Articles