أربع سنوات من تطوير SObjectizer-5.5. كيف تغير SObjectizer خلال هذا الوقت؟

تم إصدار الإصدار الأول من SObjectizer داخل فرع 5.5 قبل أكثر من أربع سنوات بقليل - في أوائل أكتوبر 2014. واليوم تم إصدار الإصدار التالي تحت الرقم 5.5.23 ، والذي من المحتمل تمامًا أن يغلق تاريخ SObjectizer-5.5. في رأيي ، هذا سبب عظيم للنظر إلى الوراء ونرى ما تم عمله على مدى السنوات الأربع الماضية.

في هذه المقالة سأحاول أن أحلل بشكل تجريدي أهم التغييرات والابتكارات المهمة: ما تمت إضافته ، ولماذا ، وكيف أثرت على SObjectizer نفسه أو استخدامه.

ربما يكون شخص ما مهتمًا بمثل هذه القصة من وجهة نظر علم الآثار. وربما يتم الاحتفاظ بشخص ما من هذه المغامرة المريبة مثل تطوير إطار الممثل الخاص به لـ C ++ ؛)

انحدار غنائي صغير حول دور جامعي C ++ القديمة


بدأ تاريخ SObjectizer-5 في منتصف عام 2010. في الوقت نفسه ، ركزنا على الفور على C ++ 0x. بالفعل في عام 2011 ، بدأ استخدام الإصدارات الأولى من SObjectizer-5 لكتابة كود الإنتاج. من الواضح أنه لم يكن لدينا مترجمون مع دعم C ++ 11 العادي في ذلك الوقت.

لفترة طويلة ، لم نتمكن من استخدام جميع ميزات "++ C الحديثة" بشكل كامل: قوالب متنوعة ، noexcept ، constexpr ، وما إلى ذلك. هذا لا يمكن أن يؤثر على SObjectizer API. وقد أثر ذلك لفترة طويلة جدًا. لذلك ، إذا كان لديك سؤال عند قراءة وصف لميزة "لماذا لم يتم ذلك من قبل؟" ، فمن المرجح أن الإجابة على هذا السؤال: "لأنه لم يكن ممكنًا من قبل".

ما الذي ظهر و / أو تغير في SObjectizer-5.5 في الماضي؟


في هذا القسم ، سنستعرض عددًا من الميزات التي كان لها تأثير كبير على SObjectizer. الترتيب في هذه القائمة عشوائي ولا يرتبط بـ "أهمية" أو "وزن" الميزات الموضحة.

رفض مساحة الاسم so_5 :: rt


ما الأمر؟


في البداية ، في SObjectizer الخامس ، تم تعريف كل ما يتعلق بوقت تشغيل SObjectizer داخل مساحة الاسم so_5 :: rt. على سبيل المثال ، كان لدينا so_5 :: rt :: environment_t ، so_5 :: rt :: agent_t ، so_5 :: rt :: message_t ، إلخ. ما يمكنك رؤيته ، على سبيل المثال ، في مثال HelloWorld التقليدي من SO-5.5.0:

#include <so_5/all.hpp> class a_hello_t : public so_5::rt::agent_t { public: a_hello_t( so_5::rt::environment_t & env ) : so_5::rt::agent_t( env ) {} void so_evt_start() override { std::cout << "Hello, world! This is SObjectizer v.5." << std::endl; so_environment().stop(); } void so_evt_finish() override { std::cout << "Bye! This was SObjectizer v.5." << std::endl; } }; int main() { try { so_5::launch( []( so_5::rt::environment_t & env ) { env.register_agent_as_coop( "coop", new a_hello_t( env ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } 

يشير الاختصار "rt" إلى وقت التشغيل. وبدا لنا أن السجل "so_5 :: rt" أفضل بكثير وأكثر عملية من "so_5 :: runtime".

ولكن اتضح أنه بالنسبة للعديد من الناس "rt" هو "الوقت الحقيقي" فقط ولا شيء آخر. واستخدام "rt" كاختصار لـ "وقت التشغيل" ينتهك مشاعرهم لدرجة أن إعلانات إصدارات SObjectizer في RuNet تحولت أحيانًا إلى holivar حول موضوع [لا] التفسير المسموح به لـ "rt" بخلاف "الوقت الحقيقي".

في النهاية ، سئمنا منها. وقمنا فقط بإلغاء تحديد مساحة الاسم so_5 :: rt.

ماذا أصبح؟


كل شيء تم تعريفه داخل "so_5 :: rt" تحول ببساطة إلى "so_5". نتيجة لذلك ، يبدو HelloWorld نفسه الآن كما يلي:

 #include <so_5/all.hpp> class a_hello_t : public so_5::agent_t { public: a_hello_t( context_t ctx ) : so_5::agent_t( ctx ) {} void so_evt_start() override { std::cout << "Hello, world! This is SObjectizer v.5 (" << SO_5_VERSION << ")" << std::endl; so_environment().stop(); } void so_evt_finish() override { std::cout << "Bye! This was SObjectizer v.5." << std::endl; } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( "coop", env.make_agent<a_hello_t>() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } 

لكن الأسماء القديمة من "so_5 :: rt" ظلت متاحة على أي حال ، من خلال المعتاد باستخدام s (typedefs). لذا فإن الشفرة المكتوبة للإصدارات الأولى من SO-5.5 قابلة للتطبيق أيضًا في الإصدارات الأخيرة من SO-5.5.

أخيرًا ، ستتم إزالة مساحة الاسم so_5 :: rt في الإصدار 5.6.

ما تأثيره؟


ربما ، أصبح الرمز الموجود على SObjectizer أكثر قابلية للقراءة الآن. ومع ذلك ، فإن so_5 :: send () أفضل من so_5 :: rt :: send ().

حسنًا ، هنا ، كما هو الحال مع مطوري SObjectizer ، انخفض الصداع. كان هناك الكثير من الأحاديث الفارغة والتفكير غير الضروري حول إعلانات SObjectizer في وقت واحد (بدءًا من الأسئلة "لماذا يحتاج الممثلون إلى C ++ بشكل عام" وينتهي بـ "لماذا لا تستخدم PascalCase لتسمية الكيانات"). أصبح موضوع قابل للاشتعال أقل وكان جيدًا :)

تبسيط إرسال الرسائل وتطور معالجات الرسائل


ما الأمر؟


حتى في الإصدارات الأولى من SObjectizer-5.5 ، تم إرسال الرسالة المعتادة باستخدام أسلوب delivery_message ، الذي كان يجب استدعاؤه على mbox الخاص بالمستلم. لإرسال رسالة معلقة أو دورية ، كان من الضروري استدعاء single_timer / Schedule_timer على كائن من النوع environment_t. ويتطلب إرسال طلب متزامن بالفعل إلى وكيل آخر بشكل عام سلسلة كاملة من العمليات. هنا ، على سبيل المثال ، كيف كان يبدو كل شيء قبل أربع سنوات (std :: make_unique () ، التي لم تكن متاحة بعد في C ++ 11 ، مستخدمة بالفعل):

 //   . mbox->deliver_message(std::make_unique<my_message>(...)); //   . env.single_timer(std::make_unique<my_message>(...), mbox, std::chrono::seconds(2)); //   . auto timer_id = env.schedule_timer( std::make_unique<my_message>(...), mbox, std::chrono::seconds(2), std::chrono::seconds(5)); //         10 . auto reply = mbox->get_one<std::string>() .wait_for(std::chrono::seconds(10)) .sync_get(std::make_unique<my_message>(...)); 

بالإضافة إلى ذلك ، تطور تنسيق معالجات الرسائل في SObjectizer إلى الإصدار 5.5. إذا كان في البداية في SObjectizer-5 ، يجب أن يكون لجميع المعالجات التنسيق:

 void evt_handler(const so_5::event_data_t<Msg> & cmd); 

ثم مع مرور الوقت ، تمت إضافة المزيد من التنسيقات المسموح بها:

 //  ,  Msg --  ,   . ret_value evt_handler(const Msg & msg); ret_value evt_handler(Msg msg); //  ,     . ret_value evt_handler(); 

أصبحت تنسيقات المعالج الجديدة مستخدمة على نطاق واسع منذ ذلك الحين الرسم باستمرار "const so_5 :: event_data_t <Msg> &" لا يزال من دواعي سروري. ولكن ، من ناحية أخرى ، لم تكن التنسيقات الأبسط صديقة لعملاء القالب. على سبيل المثال:

 template<typename Msg_To_Process> class my_actor : public so_5::agent_t { void on_receive(const Msg_To_Process & msg) { // Oops! ... } }; 

لن يعمل عامل القالب هذا إلا إذا كانت Msg_To_Process نوع رسالة وليس نوع إشارة.

ماذا أصبح؟


في الفرع 5.5 ، ظهرت عائلة من وظائف الإرسال وتطورت بشكل ملحوظ. للقيام بذلك ، أولاً ، كان علي أن أحصل على جامعي تحت تصرفي مع دعم قوالب متنوعة. وثانيًا ، لتجميع خبرة كافية في العمل مع القوالب المتنوعة بشكل عام ومع الإصدارات الأولى من وظائف الإرسال. علاوة على ذلك ، في سياقات مختلفة: في الوكلاء العاديين ، وفي الوكلاء المؤقتين ، وفي الوكلاء التي يتم تنفيذها بواسطة فئات القالب ، والوكلاء الخارجيين بشكل عام. بما في ذلك عند استخدام وظائف الإرسال مع سلاسل المفاتيح (سيتم مناقشتها أدناه).

بالإضافة إلى وظائف الإرسال ، ظهرت وظائف request_future / request_value ، وهي مصممة للتفاعل المتزامن بين الوكلاء.

ونتيجة لذلك ، أصبح إرسال الرسائل الآن كما يلي:

 //   . so_5::send<my_message>(mbox, ...); //   . so_5::send_delayed<my_message>(env, mbox, std::chrono::seconds(2), ...); //   . auto timer_id = so_5::send_periodic<my_message>( env, mbox, std::chrono::seconds(2), std::chrono::seconds(5), ...); //         10 . auto reply =so_5::request_value<std::string, my_message>(mbox, std::chrono::seconds(10), ...); 

تمت إضافة تنسيق آخر محتمل لمعالجات الرسائل. علاوة على ذلك ، سيتم ترك هذا التنسيق في الإصدارات الرئيسية التالية من SObjectizer باعتباره الإصدار الرئيسي (وربما الوحيد). هذا هو التنسيق التالي:

 ret_type evt_handler(so_5::mhood_t<Msg> cmd); 

حيث يمكن أن تكون Msg إما نوع رسالة أو نوع إشارة.

هذا التنسيق لا يطمس فقط الخط الفاصل بين الوكلاء في شكل فئات عادية ووكلاء في شكل فئات قالب. ولكنه يبسط أيضًا إعادة توجيه الرسالة / الإشارة (بفضل عائلة وظيفة الإرسال):

 void my_agent::on_msg(mhood_t<Some_Msg> cmd) { ... // -  . //       . so_5::send(another_agent, std::move(cmd)); } 

ما تأثيره؟


إن ظهور وظائف الإرسال ومعالجات الرسائل التي تستقبل mhood_t <Msg> ، يمكننا القول ، غيّر بشكل أساسي الرمز الذي يتم إرسال الرسائل ومعالجتها. هذه هي الحالة فقط عندما يبقى فقط للندم على أنه في بداية العمل على SObjectizer-5 لم يكن لدينا مترجمون يدعمون قوالب متنوعة ، ولا خبرة في استخدامها. عائلة وظائف الإرسال و mhood_t كان يجب أن تكون منذ البداية. لكن التاريخ تطور كما تطور ...

دعم أنواع الرسائل المخصصة


ما الأمر؟


في البداية ، كان من المفترض أن تكون جميع الرسائل المرسلة من الفئات المنحدرة من الفئة so_5 :: message_t. على سبيل المثال:

 struct my_message : public so_5::message_t { ... //  my_message. my_message(...) : ... {...} //   my_message. }; 

بينما تم استخدام SObjectizer الخامس من قبل أنفسنا فقط ، إلا أن هذا لم يثر أي أسئلة. حسنًا ، مثل هذا وذاك.

ولكن بمجرد أن بدأ مستخدمو الطرف الثالث في الاهتمام بـ SObjectizer ، توصلنا على الفور إلى سؤال متكرر بانتظام: "لكن يجب بالتأكيد أن أرث الرسالة من so_5 :: message_t؟" كانت هذه المشكلة ذات صلة بشكل خاص في المواقف حيث كان من الضروري إرسال كائنات من الأنواع كرسائل لا يمكن للمستخدم التأثير عليها على الإطلاق. لنفترض أن مستخدمًا يستخدم SObjectizer وبعض المكتبة الخارجية الأخرى. وفي هذه المكتبة الخارجية يوجد نوع معين M ، يرغب المستخدم في إرسال كائنات له كرسائل. حسنًا وكيف في مثل هذه الظروف تكوين صداقات من النوع M و so_5 :: message_t؟ أغلفة إضافية فقط كان على المستخدم كتابتها يدويًا.

ماذا أصبح؟


لقد أضفنا القدرة على إرسال الرسائل إلى SObjectizer-5.5 حتى إذا لم يكن نوع الرسالة موروثًا من so_5 :: message_t. على سبيل المثال الآن يمكن للمستخدم أن يكتب بسهولة:

 so_5::send<std::string>(mbox, "Hello, World!"); 

So_5 :: message_t لا تزال تحت غطاء المحرك على أي حال ، فقط بسبب القالب السحري للإرسال () يفهم أن std :: string غير موروثة من so_5 :: message_t وليس std :: سلسلة بسيطة يتم إنشاؤها داخل الإرسال ، ولكن وريث خاص من so_5 :: message_t ، حيث توجد بالفعل سلسلة std :: المطلوبة للمستخدم.

ينطبق سحر القالب المماثل على الاشتراكات. عندما يرى SObjectizer معالج رسالة من النموذج:

 void evt_handler(mhood_t<std::string> cmd) {...} 

ثم يفهم SObjectizer أنه في الواقع ستأتي رسالة خاصة مع الكائن std :: string داخل. وما تحتاجه لاستدعاء المعالج مع تمريره إلى رابط std :: string من هذه الرسالة الخاصة.

ما تأثيره؟


أصبح استخدام SObjectizer أسهل ، خاصة عندما تحتاج إلى إرسال ليس فقط كائنات من أنواعك الخاصة كرسائل ، ولكن أيضًا كتابة كائنات من مكتبات خارجية. حتى أن العديد من الأشخاص أخذوا الوقت ليقولوا لك شكرًا خاصًا على هذه الميزة.

رسائل قابلة للتغيير


ما الأمر؟


في البداية ، في SObjectizer-5 ، تم استخدام نموذج التفاعل 1: N فقط. على سبيل المثال يمكن أن تحتوي الرسالة المرسلة على أكثر من مستلم واحد (أو قد يكون هناك أكثر من واحد). حتى إذا كان الوكلاء بحاجة إلى التفاعل في وضع 1: 1 ، فإنهم ما زالوا يتواصلون من خلال صندوق بريد متعدد المنتج / متعدد المستهلكين. على سبيل المثال في 1: وضع N ، فقط N في هذه الحالة كان وحدة فقط.

في الحالات التي يمكن فيها تلقي الرسالة بواسطة أكثر من وكيل مستلم ، يجب أن تكون الرسائل المرسلة ثابتة. هذا هو السبب في أن معالجات الرسائل التنسيقات التالية:

 //       . ret_type evt_handler(const event_data_t<Msg> & cmd); //       . ret_type evt_handler(const Msg & msg); //    . //        . ret_type evt_handler(Msg msg); 

بشكل عام ، نهج بسيط ومفهوم. ومع ذلك ، فإنه ليس مناسبًا جدًا عندما يحتاج الوكلاء إلى التواصل مع بعضهم البعض في وضع 1: 1 ، على سبيل المثال ، نقل ملكية بعض البيانات إلى بعضهم البعض. لنفترض أن مثل هذه الرسالة البسيطة لا يمكن صنعها إذا كانت جميع الرسائل كائنات ثابتة تمامًا:

 struct process_image : public so_5::message_t { std::unique_ptr<gif_image> image_; process_image(std::unique_ptr<gif_image> image) : image_{std::move(image)) {} }; 

بتعبير أدق ، يمكن إرسال مثل هذه الرسالة. ولكن بعد استلامها ككائن ثابت ، لم يكن من الممكن إزالة محتويات process_image :: image_ لنفسها. أود أن أضع علامة على هذه السمة على أنها قابلة للتغيير. ولكن بعد ذلك سوف نفقد السيطرة من المحول البرمجي إذا تم إرسال process_image لسبب ما في وضع 1: N.

ماذا أصبح؟


في SObjectizer-5.5 ، تمت إضافة القدرة على إرسال واستقبال الرسائل القابلة للتغيير. في الوقت نفسه ، يجب على المستخدم تحديد الرسالة بشكل خاص عند الإرسال وعند الاشتراك فيها.

على سبيل المثال:

 //    . so_5::send<my_message>(mbox, ...); //     my_message. so_5::send<so_5::mutable_msg<my_message>>(mbox, ...); ... //     . void my_agent::on_some_event(mhood_t<my_message> cmd) {...} //      my_message. void my_agent::on_another_event(mhood_t<so_5::mutable_msg<my_message>> cmd) {...} 

بالنسبة إلى SObjectizer ، يعد my_message و mutable_msg <my_message> نوعين مختلفين من الرسائل.

عندما ترى وظيفة الإرسال أنه يُطلب منها إرسال رسالة قابلة للتغيير ، تتحقق وظيفة الإرسال لمعرفة صندوق البريد الذي تحاول إرسال الرسالة إليه. إذا كان هذا مربع متعدد العملاء ، فلا يتم الإرسال ، ولكن يتم طرح استثناء برمز الخطأ المقابل. على سبيل المثال يضمن SObjectizer أنه لا يمكن استخدام الرسائل القابلة للتغيير إلا عند التفاعل في وضع 1: 1 (عبر علب بريد أو مستهلكات أحادية المستهلك ، والتي هي شكل من صناديق البريد أحادية المستهلك). لتوفير هذا الضمان ، بالمناسبة ، يمنع SObjectizer إرسال رسائل قابلة للتغيير في شكل رسائل دورية.

ما تأثيره؟


مع الرسائل القابلة للتغيير اتضح بشكل غير متوقع. أضفناهم إلى SObjectizer نتيجة مناقشة على هامش تقرير عن SObjectizer في C ++ Russia-2017 . مع الشعور ، "حسنًا ، إذا سألوا ، فإن شخصًا ما يحتاج إليها ، لذا فإن الأمر يستحق المحاولة." حسنًا ، لقد فعلوا ذلك دون أمل كبير في طلب واسع النطاق. على الرغم من ذلك ، كان علي "تدخين الخيزران" لفترة طويلة جدًا قبل أن أفكر في كيفية إضافة رسائل قابلة للتغيير إلى SO-5.5 دون كسر التوافق.

ولكن عندما ظهرت رسائل قابلة للتغيير في SObjectizer ، اتضح أنه لم يكن هناك عدد قليل جدًا من التطبيقات لها. ويتم استخدام هذه الرسائل القابلة للتغيير بشكل مدهش في كثير من الأحيان (يمكن الإشارة إلى هذا في الجزء الثاني من القصة حول مشروع عرض الروبيان التجريبي ). لذلك في الممارسة ، كانت هذه الميزة أكثر من مفيدة ، لأنه يسمح لك بحل المشاكل التي بدون دعم الرسائل القابلة للتغيير على مستوى SObjectizer ، لم يكن لديهم حل عادي.

وكلاء آلة الدولة الهرمية


ما الأمر؟


وكلاء في SObjectizer كانوا في الأصل من آلات الدولة. كان على الوكلاء وصف الحالات صراحة والاشتراك في الرسائل في حالات محددة.
على سبيل المثال:

 class worker : public so_5::agent_t { state_t st_free{this, "free"}; state_t st_bufy{this, "busy"}; ... void so_define_agent() override { //     st_free. so_subscribe(mbox).in(st_free).event(...); //     st_busy. so_subscribe(mbox).in(st_busy).event(...); ... } }; 

لكن هذه كانت آلات حالة بسيطة. لا يمكن أن تتداخل الدول في بعضها البعض. لم يكن هناك دعم لمعالجات دخول وخروج الدولة. لم تكن هناك قيود على الوقت الذي تقضيه في الدولة.

حتى هذا الدعم المحدود لآلات الدولة كان مناسبًا واستخدمناه لأكثر من عام واحد. ولكن في مرحلة ما ، أردنا المزيد.

ماذا أصبح؟


يقدم SObjectizer الدعم لأجهزة الحالة الهرمية.

الآن يمكن دمج الدول في بعضها البعض. يتم "معالجة" معالجات الأحداث من الحالات الأصلية تلقائيًا بواسطة الحالات الفرعية.

يتم دعم معالجات الدخول والخروج من الدولة.

من الممكن وضع حد على الوقت الذي يبقى فيه الوكيل في الدولة.

من الممكن الحفاظ على تاريخ للدولة.

لكي لا يكون لها أساس من الصحة ، فيما يلي مثال على وكيل ليس آلة حالة هرمية معقدة (الرمز من المثال القياسي هو blinking_led):

 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{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } }; 

لقد وصفنا كل هذا بالفعل في مقال منفصل ؛ ليست هناك حاجة لتكراره.

لا يوجد حاليًا دعم للدول المتعامدة. لكن هذه الحقيقة لها تفسيران. أولاً ، حاولنا تقديم هذا الدعم وواجهنا عددًا من الصعوبات التي بدا أن التغلب عليها باهظ التكلفة. وثانيًا ، لم يطلب أحد الدول المتعامدة حتى الآن. عندما سئل ، ثم العودة إلى هذا الموضوع.

ما تأثيره؟


هناك شعور بأن الأمر خطير للغاية (على الرغم من أننا هنا ، بطبيعة الحال ، ذاتية وغير متحيزة). بعد كل شيء ، إنه شيء واحد ، عندما تواجه آلات الحالة المحدودة المعقدة في مجال الموضوع ، تبدأ في البحث عن حلول ، وتبسيط شيء ما ، وإنفاق قوة إضافية على شيء ما. إنها مسألة مختلفة تمامًا عندما يمكنك تعيين كائنات من تطبيقك إلى رمز C ++ الخاص بك تقريبًا 1 في 1.

بالإضافة إلى ذلك ، استنادًا إلى الأسئلة المطروحة ، على سبيل المثال ، من خلال سلوك معالجات الإدخال / الإخراج داخل / خارج الدولة ، يتم استخدام هذه الوظيفة.

مشين


ما الأمر؟


كانت حالة مثيرة للاهتمام. غالبًا ما تم استخدام SObjectizer بحيث تمت كتابة جزء فقط من التطبيق في SObjectizer. لا يمكن أن يكون لبقية التعليمات البرمجية في التطبيق أي علاقة بالممثلين بشكل عام ، أو مع SObjectizer على وجه الخصوص. على سبيل المثال ، تطبيق واجهة المستخدم الرسومية حيث يتم استخدام SObjectizer لبعض مهام الخلفية ، بينما يتم تنفيذ العمل الرئيسي على مؤشر الترابط الرئيسي للتطبيق.

وفي مثل هذه الحالات ، اتضح أنه من الجزء غير SObjectizer إلى الجزء SObjectizer ، فإن إرسال المعلومات هو أمر بسيط للغاية: يكفي استدعاء وظائف الإرسال العادية. لكن نشر المعلومات في الاتجاه المعاكس ليس بهذه البساطة. يبدو لنا أن هذا ليس جيدًا وأنه يجب أن يكون لديك بعض قنوات الاتصال المناسبة بين أجزاء SObjectizer من التطبيق والأجزاء غير SObjectizer مباشرة خارج الصندوق.

ماذا أصبح؟


لذا ، ظهرت سلاسل الرسائل ، أو في الرموز الأكثر شيوعًا ، في سلاسل SObjectizer.

يعتبر Mchain متغيرًا محددًا لصندوق بريد مستهلك فردي حيث يتم إرسال الرسائل عن طريق وظائف الإرسال العادية. ولكن لاستخراج الرسائل من mchain ، لا تحتاج إلى إنشاء وكلاء وتوقيعهم. هناك وظيفتان خاصتان يمكن تسميتهما حتى داخل الوكلاء ، حتى الوكلاء الخارجيين: استقبال () وتحديد (). يقرأ الأول الرسائل من قناة واحدة فقط ، بينما يستطيع الثاني قراءة الرسائل من عدة قنوات دفعة واحدة:

 using namespace so_5; mchain_t ch1 = env.create_mchain(...); mchain_t ch2 = env.create_mchain(...); select( from_all().handle_n(3).empty_timeout(200ms), case_(ch1, [](mhood_t<first_message_type> msg) { ... }, [](mhood_t<second_message_type> msg) { ... }), case_(ch2, [](mhood_t<third_message_type> msg ) { ... }, [](mhood_t<some_signal_type>){...}, ... )); 

لقد تحدثنا بالفعل عن mchain عدة مرات هنا: في أغسطس 2017 ومايو 2018 . لذلك ، خاصةً حول موضوع كيفية العمل مع سلاسل المفاتيح ، لن نتعمق هنا.

ما تأثيره؟


بعد ظهور mchains في SObjectizer-5.5 ، اتضح أن SObjectizer ، في الواقع ، أصبح إطار "ممثل" أقل مما كان عليه من قبل. بالإضافة إلى دعم نموذج الممثل و Pub / Sub ، أضاف SObjectizer أيضًا دعمًا لنموذج CSP (توصيل العمليات المتسلسلة). تتيح لك Mchains تطوير تطبيقات متعددة الخيوط معقدة إلى حد ما على SObjectizer دون أي ممثلين على الإطلاق. وبالنسبة لبعض المهام ، يعد هذا أكثر من ملائم. ما نستخدمه نحن أنفسنا من وقت لآخر.

آلية حدود الرسائل


ما الأمر؟


واحدة من أخطر أوجه القصور في نموذج الممثلين هي استعداده للحمل الزائد. من السهل جدًا أن تجد نفسك في موقف يرسل فيه الفاعل المرسل رسائل إلى الفاعل المتلقي بوتيرة أسرع من قدرة الفاعل المتلقي على معالجة الرسائل.

كقاعدة ، يعد إرسال الرسائل في أطر الممثل عملية لا تمنع. لذلك ، عندما يحدث زوج "منتج ذكى ومستهلك نردي" ، ستزداد قائمة انتظار الممثل المتلقي بينما هناك على الأقل بعض الذاكرة الحرة.

وتتمثل الصعوبة الرئيسية لهذه المشكلة في أنه يجب تحسين آلية جيدة للحماية من الحمل الزائد للمهمة المطبقة وخصائص مجال الموضوع. على سبيل المثال ، لفهم الرسائل التي يمكن تكرارها (وبالتالي القدرة على تجاهل النسخ المكررة بأمان). لفهم الرسائل التي لا يمكن التخلص منها على أي حال. من الذي يمكن تعليقه وكم ، ومن الذي لا يسمح على الإطلاق. إلخ ، إلخ.

الصعوبة الأخرى هي أنه ليس من الضروري دائمًا وجود آلية دفاع جيدة. في بعض الأحيان ، يكفي أن يكون لديك شيء بدائي ، ولكنه فعال ويمكن الوصول إليه "من خارج الصندوق" وسهل الاستخدام. لكي لا تجبر المستخدم على التحكم في التحميل الزائد حيث يكفي ببساطة التخلص من الرسائل "الإضافية" أو إعادة توجيه هذه الرسائل إلى وكيل آخر.

ماذا أصبح؟


فقط حتى في سيناريوهات بسيطة يمكنك استخدام أدوات حماية الزائد الجاهزة ، ما يسمى حدود الرسالة. تسمح لك هذه الآلية بتجاهل الرسائل غير الضرورية ، أو إرسالها إلى مستلمين آخرين ، أو حتى مقاطعة التطبيق ببساطة إذا تم تجاوز الحدود. على سبيل المثال:

 class worker : public so_5::agent_t { public: worker(context_t ctx) : so_5::agent_t{ ctx //     100  handle_data, //      //  . + limit_then_redirect<handle_data>(100, [this]{ return another_worker_;}) //     1  check_status, //       . + limit_then_drop<check_status>(1) //     1  reconfigure, //     , ..  reconfigure //        . + limit_then_abore<reconfigure>(1) } {...} ... }; 

يتم وصف هذا الموضوع بمزيد من التفصيل في مقال منفصل .

ما تأثيره؟


هذا لا يعني أن ظهور حدود الرسائل أصبح شيئًا قد غيّر بشكل أساسي SObjectizer ، مبادئ عمله أو العمل معه. بدلاً من ذلك ، يمكن مقارنته بمظلة احتياطية ، والتي يتم استخدامها فقط كملاذ أخير. ولكن عندما تضطر إلى استخدامه ، فأنت سعيد لأنه موجود حتى.

آلية تتبع تسليم الرسائل


ما الأمر؟


كان SObjectizer-5 صندوقًا أسود للمطورين. حيث يتم إرسال الرسالة و ... إما أنها تصل إلى المستلم أو لا تأتي.

إذا لم تصل الرسالة إلى المستلم ، فسيواجه المستخدم الحاجة للذهاب في مهمة مثيرة بحثًا عن سبب. في معظم الحالات ، تكون الأسباب تافهة: إما أنه تم إرسال الرسالة إلى mbox غير صحيح ، أو لم يتم الاشتراك (على سبيل المثال ، قام المستخدم بعمل اشتراك في إحدى حالات الوكيل ، لكنه نسي القيام بذلك في دولة أخرى). ولكن قد تكون هناك حالات أكثر تعقيدًا عندما يتم رفض الرسالة ، على سبيل المثال ، بواسطة آلية الحماية من التحميل الزائد.

كانت المشكلة أن آلية تسليم الرسائل كانت مخبأة في أعماق حوصرات SObjectizer Run-Time ، وبالتالي كان من الصعب حتى على مطوري SObjectizer توجيه الرسالة إلى المستلم ، ناهيك عن المستخدمين. خاصة حول المستخدمين المبتدئين الذين ارتكبوا أكبر عدد من هذه الأخطاء التافهة.

ماذا أصبح؟


في SObjectizer-5.5 ، تمت إضافة آلية خاصة لتتبع عملية تسليم الرسائل تسمى تتبع تسليم الرسائل (أو ببساطة msg_tracing) ، ثم تم إنهاؤها. تم وصف هذه الآلية وقدراتها بمزيد من التفصيل في مقال منفصل .

حتى الآن ، إذا فقدت الرسائل عند التسليم ، يمكنك ببساطة تمكين msg_tracing ومعرفة سبب حدوث ذلك.

ما تأثيره؟


أصبحت تطبيقات تصحيح الأخطاء المكتوبة بلغة SObjectizer أبسط وأكثر متعة. حتى لأنفسنا .

مفهوم البنية التحتية env_inf البنية التحتية و env_infrastructure


ما الأمر؟


لقد اعتبرنا دائمًا SObjectizer كأداة لتبسيط تطوير التعليمات البرمجية متعددة الخيوط. لذلك ، تمت كتابة الإصدارات الأولى من SObjectizer-5 للعمل فقط في بيئة متعددة الخيوط.

تم التعبير عن ذلك على أنه استخدام بدائية التزامن داخل SObjectizer لحماية الأجزاء الداخلية لـ SObjectizer عند العمل في بيئة متعددة الخيوط. لذا فهو من خلال إنشاء العديد من خيوط العمل المساعدة داخل SObjectizer نفسه (لأداء عمليات مهمة مثل خدمة المؤقت واستكمال إلغاء تسجيل تعاون الوكيل).

على سبيل المثالتم إنشاء SObjectizer للبرمجة متعددة الخيوط وللاستخدام في البيئات المتعددة الخيوط. وهذا يناسبنا تمامًا.

ومع ذلك ، حيث تم استخدام SObjectizer "في البرية" ، تم اكتشاف المواقف حيث كانت المهمة صعبة بما يكفي لاستخدام الممثلين في حلها. ولكن ، في الوقت نفسه ، كان من الممكن تنفيذ كل العمل ، علاوة على ذلك ، على سير عمل واحد.

وواجهنا مشكلة مثيرة للاهتمام للغاية: هل من الممكن تعليم SObjectizer للعمل على خيط عمل واحد؟

ماذا أصبح؟


اتضح أنه من الممكن.

لقد كلفنا الكثير من المال ، واستغرق الأمر الكثير من الوقت والجهد للتوصل إلى حل. ولكن تم اختراع الحل.

تم تقديم مفهوم مثل البنية التحتية البيئية (أو env_infrastructure بشكل مختصر قليلاً). تولى Env_infrastructure مهمة إدارة مطبخ SObjectizer الداخلي. على وجه الخصوص ، قام بحل قضايا مثل خدمة الموقتات ، وأداء تسجيل وإلغاء تسجيل التعاونيات.

بالنسبة إلى SObjectizer ، تم إجراء العديد من خيارات البنية التحتية env_inf threaded. هذا سمح لنا بتطوير تطبيقات مفردة الخيوط على SObjectizer ، يوجد داخلها وكلاء عاديون يتبادلون الرسائل العادية مع بعضهم البعض.

تحدثنا عن هذه الوظيفة بمزيد من التفصيل في مقالة منفصلة. .

ما تأثيره؟


ربما كان أهم شيء حدث أثناء تنفيذ هذه الميزة هو كسر القوالب الخاصة بنا. نظرة على SObjectizer لن تكون هي نفسها. لسنوات عديدة ، ضع في اعتبارك SObjectizer حصريًا كأداة لتطوير كود متعدد الخيوط. ثم مرة أخرى! وتجد أنه يمكن أيضًا تطوير رمز أحادي الخيوط على SObjectizer. الحياة مليئة بالمفاجآت.

أدوات مراقبة وقت التشغيل


ما الأمر؟


كان SObjectizer-5 صندوقًا أسودًا ليس فقط من حيث آلية تسليم الرسائل. ولكن لا توجد أيضًا طريقة لمعرفة عدد الوكلاء الذين يعملون حاليًا داخل التطبيق ، وعدد المرسلين وأيهم يتم إنشاؤهم ، وعدد سلاسل عمليات العمل المعنية ، وعدد الرسائل التي تنتظر في قوائم انتظار المرسلين ، وما إلى ذلك.

كل هذه المعلومات مفيدة جدًا لمراقبة التطبيقات التي تعمل على مدار الساعة طوال أيام الأسبوع. ولكن بالنسبة إلى تصحيح الأخطاء ، أود أيضًا أن أفهم من وقت لآخر ما إذا كانت قوائم الانتظار في تزايد أم أن عدد العملاء يتزايد / يتناقص.

لسوء الحظ ، في الوقت الحالي ، لم تصل أيدينا ببساطة إلى نقطة إضافة الأموال إلى SObjectizer لجمع ونشر هذه المعلومات.

ماذا أصبح؟


عند نقطة واحدة في SObjectizer-5.5 ، ظهرت أدوات لمراقبة وقت التشغيل الداخلي لـ SObjectizer . افتراضيًا ، يتم تعطيل مراقبة وقت التشغيل ، ولكن إذا قمت بتمكينها ، فسيتم إرسال الرسائل إلى صندوق البريد الخاص بشكل منتظم ، والتي ستوجد داخلها معلومات حول عدد الوكلاء والتعاون ، وعدد الموقتات ، وعلى سلاسل العمل التي يملكها المرسلون (وستكون هناك بالفعل معلومات حول عدد الرسائل في قوائم الانتظار ، عدد الوكلاء المرتبطين بهذه المواضيع).

بالإضافة إلى ذلك ، مع مرور الوقت ، أصبح من الممكن أيضًا تمكين جمع المعلومات حول مقدار الوقت الذي يقضيه الوكلاء داخل معالجات الأحداث. يسمح لك هذا باكتشاف المواقف عندما يكون بعض الوكيل بطيئًا جدًا (أو يضيع الوقت في حظر المكالمات).

ما تأثيره؟


في ممارستنا ، لا يتم استخدام مراقبة وقت التشغيل في كثير من الأحيان. ولكن عندما تحتاج إليها ، فإنك تدرك أهميتها. في الواقع ، بدون هذه الآلية ، من المستحيل (جيدًا ، أو صعبًا جدًا) معرفة ما وكيف [لا] يعمل.

إذن هذه ميزة من فئة "يمكنك القيام بذلك" ، لكن وجودها ، في رأينا ، ينقل الأداة على الفور إلى فئة وزن أخرى. لأنصنع نموذج أولي لإطار الممثل "على الركبة" ليس بالأمر الصعب. لقد فعل كثيرون ذلك وسيفعله الكثيرون. ولكن بعد ذلك لتجهيز تطويرك بشيء مثل مراقبة وقت التشغيل ... حتى الآن ، لا تنجو جميع المسودات التي تجرها الركبة.

وشيء آخر في سطر واحد


لمدة أربع سنوات ، حصل SObjectizer-5.5 على الكثير من الابتكارات والتغييرات ، والتي سيستغرق وصفها ، حتى في ملخص ، مساحة كبيرة جدًا. لذلك ، نشير إلى جزء منها بخط واحد حرفيا. بترتيب عشوائي ، دون أي أولويات.

يضيف SObjectizer-5.5 دعمًا لنظام إنشاء CMake.

يمكن الآن بناء SObjectizer-5 على شكل مكتبة ديناميكية وثابتة.

تم إنشاء SObjectizer-5.5 الآن وتشغيله على Android (سواء من خلال CrystaX NDK ، أو من خلال Android NDK الجديد).

ظهرت المرسلات الخاصة. الآن يمكنك إنشاء واستخدام المرسلات التي لا يراها أي شخص آخر.

نفذت آلية مرشحات التسليم. الآن ، عند الاشتراك في الرسائل من MPMC-mboxes ، يمكنك منع تسليم الرسائل التي لا تهتم بمحتوياتها.

تم تبسيط أدوات إنشاء عمليات التعاون وتسجيلها بشكل ملحوظ: الطرق introco / intr_child_coop و make_agent / make_agent_with_binder وكل ذلك.

ظهر مفهوم مصنع كائنات القفل والآن يمكنك اختيار أي من كائنات القفل التي تحتاجها (استنادًا إلى كائن المزامنة ، أو spinlock ، أو مجتمعة أو غيرها).

ظهرت فئة wrapped_env_t والآن يمكنك تشغيل SObjectizer في تطبيقك ليس فقط مع so_5 :: launch ().

ظهر مفهوم stop_guards والآن يمكنك التأثير على عملية إغلاق SObjectizer. على سبيل المثال ، يمكنك منع SObjectizer من التوقف حتى يكمل بعض الوكلاء عمل التطبيق الخاص بهم.

يمكنك الآن اعتراض الرسائل التي تم تسليمها إلى الوكيل ولكن لم يتم معالجتها بواسطة الوكيل (ما يسمى dead_letter_handlers).

كانت هناك فرصة لف الرسائل في "مغلفات" خاصة. يمكن أن تحمل المغلفات معلومات إضافية حول الرسالة ويمكنها تنفيذ بعض الإجراءات عند تسليم الرسالة إلى المستلم.

5.5.0 إلى 5.5.23 في الأرقام


من المثير للاهتمام أيضًا أن ننظر إلى المسار الذي تم إجراؤه من حيث الكود / الاختبارات / الأمثلة. إليك ما تخبرنا به أداة cloc عن مقدار كود نواة SObjectizer-5.5.0:

 -------------------------------------------------- -----------------------------
ملفات اللغة رمز التعليق فارغة
-------------------------------------------------- -----------------------------
رأس C / C ++ 58 2119 5156 5762
C ++ 3911677779 4759
روبي 2 30 2 75
-------------------------------------------------- -----------------------------
المجموع: 99 3316 5937 10596
-------------------------------------------------- -----------------------------

وهنا نفس الشيء ، ولكن بالنسبة للإصدار 5.5.23 (منها 1147 سطرًا هي رمز المكتبة الاختيارية الخفيفة):
 -------------------------------------------------- -----------------------------
ملفات اللغة رمز التعليق فارغة
-------------------------------------------------- -----------------------------
رأس C / C ++ 133 6279 22173 21068
C ++ 53 2498 2760 10398
كميك 2 29 0 177
روبي 4 53 2129
-------------------------------------------------- -----------------------------
المجموع: 192 8859 24935 31772
-------------------------------------------------- -----------------------------

اختبارات حجم v.5.5.0:
 -------------------------------------------------- -----------------------------
ملفات اللغة رمز التعليق فارغة
-------------------------------------------------- -----------------------------
C ++ 84 2510390 11540
روبي 162496 1054
رأس C / C ++ 1 11 0 32
-------------------------------------------------- -----------------------------
المجموع: 247 3017390 12626
-------------------------------------------------- -----------------------------

اختبارات v.5.5.23:
 -------------------------------------------------- -----------------------------
ملفات اللغة رمز التعليق فارغة
-------------------------------------------------- -----------------------------
C ++ 324 7345 1305 35231
روبي 675 2،353 0 4،671
كميك 338 43 0 955
رأس C / C ++ 11107 3444
-------------------------------------------------- -----------------------------
المجموع: 1348 9848 1308 41305

حسنًا ، أمثلة على الإصدار 5.5.0:
 -------------------------------------------------- -----------------------------
ملفات اللغة رمز التعليق فارغة
-------------------------------------------------- -----------------------------
C ++ 27765463 3322
روبي 28 95 0219
-------------------------------------------------- -----------------------------
المجموع: 55860463 3514

هم ، ولكن بالفعل ل v.5.5.23:
 -------------------------------------------------- -----------------------------
ملفات اللغة رمز التعليق فارغة
-------------------------------------------------- -----------------------------
C ++ 67 2141 2061 9341
روبي 133451 0668
كميك 67 93 0595
رأس C / C ++ 1 12 11 32
-------------------------------------------------- -----------------------------
المجموع: 268 2697 2072 10836

في كل مكان تقريبًا ، زيادة تقارب ثلاث مرات.

وربما زادت كمية وثائق SObjectizer أكثر من ذلك.

خطط للمستقبل القريب (وليس فقط)


تم وصف خطط التطوير الأولية لـ SObjectizer بعد إصدار الإصدار 5.5.23 هنا قبل حوالي شهر. بشكل أساسي ، لم يتغيروا. ولكن كان هناك شعور بأن الإصدار 5.6.0 ، الذي تم التخطيط لإصداره في بداية عام 2019 ، سيحتاج إلى وضعه كبداية للفرع المستقر التالي من SObjectizer. مع مراعاة حقيقة أنه خلال عام 2019 ، سوف يتطور SObjectizer تحت الفرع 5.6 دون أي تغييرات كسر كبيرة.

سيمكن هذا أولئك الذين يستخدمون الآن SO-5.5 في مشاريعهم من التحول تدريجيًا إلى SO-5.6 دون خوف من أنه سيتعين عليهم التبديل إلى SO-5.7 أيضًا.

الإصدار 5.7 ، الذي نريد فيه السماح لأنفسنا بالابتعاد في مكان ما عن المبادئ الأساسية لـ SO-5.5 و SO-5.6 ، سيتم اعتباره تجريبيًا في عام 2019. مع الاستقرار والإطلاق ، إذا سارت الأمور على ما يرام ، بالفعل في العام 2020.

الخلاصة


في الختام ، أود أن أشكر كل من ساعدنا في تطوير SObjectizer طوال هذا الوقت. وأود أن أقول شكرا بشكل منفصل لجميع أولئك الذين تجرأوا على محاولة العمل مع SObjectizer. ملاحظاتك كانت دائما مفيدة جدا لنا.

نريد أن نقول لأولئك الذين لم يستخدموا SObjectizer بعد: جربها. هذا ليس مخيفا كما قد يبدو.

إذا لم يعجبك شيء أو لم يكن لديك ما يكفي في SObjectizer - أخبرنا. نستمع دائما إلى النقد البناء. وإذا كان في وسعنا ، فإننا ننقل رغبات المستخدمين إلى الحياة.

Source: https://habr.com/ru/post/ar429046/


All Articles