Die erste Version von SObjectizer im Rahmen von Branch 5.5 wurde vor etwas mehr als vier Jahren veröffentlicht - Anfang Oktober 2014. Und heute wurde
die nächste Version unter der Nummer 5.5.23 veröffentlicht , die möglicherweise die Geschichte von SObjectizer-5.5 schließen wird. Meiner Meinung nach ist dies ein guter Grund, zurückzublicken und zu sehen, was in den letzten vier Jahren getan wurde.
In diesem Artikel werde ich versuchen, die wichtigsten und wichtigsten Änderungen und Innovationen abstrakt zu analysieren: Was wurde hinzugefügt, warum, wie hat sich dies auf den SObjectizer selbst oder seine Verwendung ausgewirkt?
Vielleicht interessiert sich jemand für eine solche Geschichte aus archäologischer Sicht. Und vielleicht wird jemand von einem so zweifelhaften Abenteuer wie der Entwicklung seines eigenen Schauspieler-Frameworks für C ++ abgehalten;)
Ein kleiner lyrischer Exkurs über die Rolle alter C ++ - Compiler
Die Geschichte von SObjectizer-5 begann Mitte 2010. Gleichzeitig haben wir uns sofort auf C ++ 0x konzentriert. Bereits 2011 wurden die ersten Versionen von SObjectizer-5 zum Schreiben von Produktionscode verwendet. Es ist klar, dass wir damals keine Compiler mit normaler C ++ 11-Unterstützung hatten.
Lange Zeit konnten wir nicht alle Funktionen von "modernem C ++" vollständig nutzen: verschiedene Vorlagen, noexcept, constexpr usw. Dies konnte die SObjectizer-API nur beeinflussen. Und es hat sehr, sehr lange gedauert. Wenn Sie beim Lesen einer Beschreibung einer Funktion die Frage "Warum wurde sie noch nicht ausgeführt?" Haben, lautet die Antwort auf diese Frage höchstwahrscheinlich: "Weil dies vorher nicht möglich war".
Was ist in SObjectizer-5.5 in der Vergangenheit erschienen und / oder hat sich geändert?
In diesem Abschnitt werden einige Funktionen behandelt, die sich erheblich auf SObjectizer ausgewirkt haben. Die Reihenfolge in dieser Liste ist zufällig und hängt nicht mit der "Bedeutung" oder dem "Gewicht" der beschriebenen Merkmale zusammen.
Ablehnen des so_5 :: rt-Namespace
Was war?
Im fünften SObjectizer wurde zunächst alles, was mit der SObjectizer-Laufzeit zu tun hat, im so_5 :: rt-Namespace definiert. Zum Beispiel hatten wir so_5 :: rt :: environment_t, so_5 :: rt :: agent_t, so_5 :: rt :: message_t usw. Was Sie zum Beispiel im traditionellen HelloWorld-Beispiel aus SO-5.5.0 sehen können:
#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; }
Die Abkürzung "rt" steht für Laufzeit. Und es schien uns, dass die Aufzeichnung "so_5 :: rt" viel besser und praktischer ist als "so_5 :: runtime".
Es stellte sich jedoch heraus, dass „rt“ für viele Menschen nur „Echtzeit“ und sonst nichts ist. Und die Verwendung von "rt" als Abkürzung für "runtime" verletzt ihre Gefühle so sehr, dass die Ankündigungen der Versionen von SObjectizer in RuNet manchmal zu einem Holivar zum Thema [nicht] zulässige Interpretation von "rt" außer "Echtzeit" wurden.
Am Ende haben wir es satt. Und wir haben gerade den so_5 :: rt-Namespace deprimiert.
Was ist geworden?
Alles, was in "so_5 :: rt" definiert wurde, wurde einfach auf "so_5" umgestellt. Infolgedessen sieht dieselbe HelloWorld jetzt folgendermaßen aus:
#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; }
Aber die alten Namen von "so_5 :: rt" blieben trotzdem verfügbar, durch die übliche Verwendung von s (typedefs). Daher kann der für die ersten Versionen von SO-5.5 geschriebene Code auch in neueren Versionen von SO-5.5 verwendet werden.
Schließlich wird der Namespace so_5 :: rt in Version 5.6 entfernt.
Welchen Einfluss hatte es?
Wahrscheinlich ist der Code in SObjectizer jetzt besser lesbar. Trotzdem wird so_5 :: send () besser wahrgenommen als so_5 :: rt :: send ().
Nun, hier, wie bei den SObjectizer-Entwicklern, haben die Kopfschmerzen abgenommen. Es gab zu viele leere Chatter und unnötige Überlegungen zu SObjectizers Ankündigungen auf einmal (beginnend mit den Fragen „Warum Schauspieler in C ++ im Allgemeinen benötigt werden“ und endend mit „Warum verwenden Sie PascalCase nicht, um Entitäten zu benennen“). Ein brennbares Thema wurde weniger und es war gut :)
Vereinfachen Sie das Senden von Nachrichten und die Entwicklung von Nachrichtenhandlern
Was war?
Bereits in den allerersten Versionen von SObjectizer-5.5 wurde die übliche Nachricht mit der Methode Deliver_message gesendet, die auf der mbox des Empfängers aufgerufen werden musste. Um eine ausstehende oder periodische Nachricht zu senden, musste single_timer / Schedule_timer für ein Objekt vom Typ environment_t aufgerufen werden. Das Senden einer synchronen Anforderung an einen anderen Agenten erforderte im Allgemeinen eine ganze Kette von Vorgängen. Hier zum Beispiel, wie alles vor vier Jahren ausgesehen haben könnte (std :: make_unique (), das in C ++ 11 noch nicht verfügbar war, wird bereits verwendet):
Darüber hinaus wurde das Format der Nachrichtenhandler in SObjectizer auf Version 5.5 weiterentwickelt. Wenn anfänglich in SObjectizer-5, sollten alle Handler das Format haben:
void evt_handler(const so_5::event_data_t<Msg> & cmd);
dann wurden im Laufe der Zeit einige weitere zu den zulässigen Formaten hinzugefügt:
Seitdem sind neue Handlerformate weit verbreitet Es ist immer noch ein Vergnügen, ständig „const so_5 :: event_data_t <Msg> &“ zu malen. Auf der anderen Seite waren einfachere Formate für die Vorlagenagenten nicht geeignet. Zum Beispiel:
template<typename Msg_To_Process> class my_actor : public so_5::agent_t { void on_receive(const Msg_To_Process & msg) {
Ein solcher Vorlagenagent funktioniert nur, wenn Msg_To_Process ein Nachrichtentyp und kein Signaltyp ist.
Was ist geworden?
In Zweig 5.5 erschien eine Familie von Sendefunktionen, die sich signifikant weiterentwickelten. Dazu musste ich zunächst Compiler mit Unterstützung für verschiedene Vorlagen zur Verfügung stellen. Und zweitens, um ausreichende Erfahrung sowohl mit variadischen Vorlagen im Allgemeinen als auch mit den ersten Versionen von Sendefunktionen zu sammeln. Darüber hinaus in verschiedenen Kontexten: in normalen Agenten und in Ad-hoc-Agenten sowie in Agenten, die von Vorlagenklassen implementiert werden, und in externen Agenten im Allgemeinen. Einschließlich bei Verwendung von Sendefunktionen mit Ketten (diese werden unten erläutert).
Zusätzlich zu den Sendefunktionen wurden Funktionen für request_future / request_value angezeigt, die für die synchrone Interaktion zwischen Agenten ausgelegt sind.
Infolgedessen werden Nachrichten jetzt wie folgt gesendet:
Ein weiteres mögliches Format für Nachrichtenhandler wurde hinzugefügt. Darüber hinaus wird dieses Format in den nächsten Hauptversionen von SObjectizer als Hauptformat (und möglicherweise als einziges) beibehalten. Dies ist das folgende Format:
ret_type evt_handler(so_5::mhood_t<Msg> cmd);
Dabei kann Msg entweder ein Nachrichtentyp oder ein Signaltyp sein.
Dieses Format verwischt nicht nur die Grenze zwischen Agenten in Form von normalen Klassen und Agenten in Form von Vorlagenklassen. Es vereinfacht aber auch die Weiterleitung der Nachricht / des Signals (dank der Sendefunktionsfamilie):
void my_agent::on_msg(mhood_t<Some_Msg> cmd) { ...
Welchen Einfluss hatte es?
Das Auftreten von Sendefunktionen und Nachrichtenhandlern, die mhood_t <Msg> empfangen, hat den Code, in dem Nachrichten gesendet und verarbeitet werden, grundlegend geändert. Dies ist nur dann der Fall, wenn nur zu bedauern bleibt, dass wir zu Beginn der Arbeit an SObjectizer-5 weder Compiler mit Unterstützung für verschiedene Vorlagen hatten noch Erfahrung in deren Verwendung hatten. Die Familie der Sendefunktionen und mhood_t sollte von Anfang an vorhanden sein. Aber die Geschichte hat sich so entwickelt, wie sie sich entwickelt hat ...
Unterstützung für benutzerdefinierte Nachrichtentypen
Was war?
Ursprünglich sollten alle gesendeten Nachrichten Nachkommenklassen der Klasse so_5 :: message_t sein. Zum Beispiel:
struct my_message : public so_5::message_t { ...
Während der fünfte SObjectizer nur von uns selbst verwendet wurde, warf dies keine Fragen auf. Nun, so und so.
Sobald sich Benutzer von Drittanbietern für SObjectizer interessierten, stießen wir sofort auf eine sich regelmäßig wiederholende Frage: "Aber ich muss die Nachricht definitiv von so_5 :: message_t erben?" Dieses Problem war besonders relevant in Situationen, in denen Objekte vom Typ als Nachrichten gesendet werden mussten, die der Benutzer überhaupt nicht beeinflussen konnte. Angenommen, ein Benutzer verwendet einen SObjectizer und eine andere externe Bibliothek. Und in dieser externen Bibliothek gibt es einen bestimmten Typ M, dessen Objekte der Benutzer als Nachrichten senden möchte. Nun und wie kann man unter solchen Bedingungen Freunde finden, die M und so_5 :: message_t eingeben? Nur zusätzliche Wrapper, die der Benutzer manuell schreiben musste.
Was ist geworden?
Wir haben die Möglichkeit hinzugefügt, Nachrichten an SObjectizer-5.5 zu senden, auch wenn der Nachrichtentyp nicht von so_5 :: message_t geerbt wird. Das heißt, Jetzt kann der Benutzer einfach schreiben:
so_5::send<std::string>(mbox, "Hello, World!");
So_5 :: message_t bleibt ohnehin unter der Haube, nur weil die Vorlage magic send () versteht, dass std :: string nicht von so_5 :: message_t geerbt wird und kein einfacher std :: string in send konstruiert wird, sondern ein spezieller Erbe von so_5 :: message_t, in dem sich bereits der vom Benutzer gewünschte std :: string befindet.
Ähnliche Vorlagenmagie gilt für Abonnements. Wenn SObjectizer einen Nachrichtenhandler des Formulars sieht:
void evt_handler(mhood_t<std::string> cmd) {...}
dann versteht SObjectizer, dass tatsächlich eine spezielle Nachricht mit dem darin enthaltenen std :: string-Objekt kommt. Und was Sie brauchen, um den Handler aufzurufen, indem Sie ihm einen Link zu std :: string aus dieser speziellen Nachricht übergeben.
Welchen Einfluss hatte es?
Die Verwendung von SObjectizer ist einfacher geworden, insbesondere wenn Sie nicht nur Objekte Ihres eigenen Typs als Nachrichten senden müssen, sondern auch Objekte aus externen Bibliotheken. Einige Leute haben sich sogar die Zeit genommen, sich ganz besonders für diese Funktion zu bedanken.
Veränderbare Nachrichten
Was war?
Anfangs wurde in SObjectizer-5 nur das 1: N-Interaktionsmodell verwendet. Das heißt, Eine gesendete Nachricht kann mehr als einen Empfänger haben (oder es kann mehr als einen geben). Selbst wenn Agenten im 1: 1-Modus interagieren mussten, kommunizierten sie dennoch über ein Postfach mit mehreren Herstellern und mehreren Verbrauchern. Das heißt, im 1: N-Modus war in diesem Fall nur N eine Einheit.
Unter Bedingungen, unter denen eine Nachricht von mehr als einem Empfängeragenten empfangen werden kann, müssen die gesendeten Nachrichten unveränderlich sein. Aus diesem Grund hatten Nachrichtenhandler die folgenden Formate:
Im Allgemeinen ein einfacher und verständlicher Ansatz. Es ist jedoch nicht sehr praktisch, wenn Agenten im 1: 1-Modus miteinander kommunizieren und beispielsweise das Eigentum an einigen Daten untereinander übertragen müssen. Nehmen wir an, eine so einfache Nachricht kann nicht erstellt werden, wenn alle Nachrichten streng unveränderliche Objekte sind:
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)) {} };
Genauer gesagt könnte eine solche Nachricht gesendet werden. Nachdem es jedoch als konstantes Objekt empfangen wurde, wäre es nicht möglich gewesen, den Inhalt von process_image :: image_ für sich zu entfernen. Ich müsste ein solches Attribut als veränderlich markieren. Aber dann würden wir die Kontrolle über den Compiler verlieren, wenn process_image aus irgendeinem Grund im 1: N-Modus gesendet wird.
Was ist geworden?
In SObjectizer-5.5 wurde die Möglichkeit zum Ändern und Empfangen veränderlicher Nachrichten hinzugefügt. Gleichzeitig muss der Benutzer die Nachricht sowohl beim Senden als auch beim Abonnieren speziell markieren.
Zum Beispiel:
Für SObjectizer sind my_message und mutable_msg <my_message> zwei verschiedene Arten von Nachrichten.
Wenn eine Sendefunktion erkennt, dass sie zum Senden einer veränderlichen Nachricht aufgefordert wird, prüft die Sendefunktion, an welches Postfach sie die Nachricht senden möchte. Wenn es sich um eine Multi-Consumer-Box handelt, wird das Senden nicht ausgeführt, es wird jedoch eine Ausnahme mit dem entsprechenden Fehlercode ausgelöst. Das heißt, SObjectizer stellt sicher, dass veränderbare Nachrichten nur bei der Interaktion im 1: 1-Modus verwendet werden können (über Einzelverbraucher-Postfächer oder Ketten, die eine Form von Einzelverbraucher-Postfächern sind). Um diese Garantie zu gewährleisten, verbietet SObjectizer übrigens das Senden veränderlicher Nachrichten in Form von regelmäßigen Nachrichten.
Welchen Einfluss hatte es?
Bei veränderlichen Nachrichten stellte sich dies unerwartet heraus. Wir haben sie als Ergebnis einer Diskussion am Rande eines
Berichts über SObjectizer auf der C ++ Russia-2017 zu SObjectizer hinzugefügt. Mit dem Gefühl: "Nun, wenn sie fragen, dann braucht es jemand, also ist es einen Versuch wert." Nun, sie hatten keine große Hoffnung auf eine breite Nachfrage. Obwohl ich dafür sehr lange "Bambus rauchen" musste, bevor ich darüber nachdachte, wie ich SO-5.5 veränderbare Nachrichten hinzufügen kann, ohne die Kompatibilität zu beeinträchtigen.
Als jedoch veränderbare Nachrichten in SObjectizer auftauchten, stellte sich heraus, dass es nicht so wenige Anwendungen für sie gab. Und dass veränderliche Nachrichten überraschend oft verwendet werden (Erwähnung findet sich
im zweiten Teil der Geschichte über das Shrimp-Demo-Projekt ). In der Praxis war diese Funktion mehr als nützlich, weil Sie können damit Probleme lösen, die ohne die Unterstützung veränderlicher Nachrichten auf SObjectizer-Ebene keine normale Lösung hatten.
Hierarchische Zustandsmaschinenagenten
Was war?
Agenten in SObjectizer waren ursprünglich Zustandsautomaten. Agenten mussten Zustände explizit beschreiben und Nachrichten in bestimmten Zuständen abonnieren.
Zum Beispiel:
class worker : public so_5::agent_t { state_t st_free{this, "free"}; state_t st_bufy{this, "busy"}; ... void so_define_agent() override {
Aber das waren einfache Zustandsmaschinen. Staaten konnten nicht ineinander verschachtelt werden. Es gab keine Unterstützung für staatliche Ein- und Ausreiseverwalter. Es gab keine Einschränkungen hinsichtlich der im Staat verbrachten Zeit.
Selbst eine derart begrenzte Unterstützung für Zustandsautomaten war praktisch und wir haben sie mehr als ein Jahr lang verwendet. Aber irgendwann wollten wir mehr.
Was ist geworden?
SObjectizer bietet Unterstützung für hierarchische Zustandsautomaten.
Jetzt können Zustände ineinander verschachtelt werden. Ereignishandler aus übergeordneten Zuständen werden automatisch von untergeordneten Zuständen "geerbt".
Handler zum Eingeben und Verlassen eines Status werden unterstützt.
Es ist möglich, ein Limit für die Zeit festzulegen, in der der Agent im Status bleibt.
Es ist möglich, eine Geschichte für den Staat zu führen.
Um nicht unbegründet zu sein, hier ein Beispiel für einen Agenten, der keine komplexe hierarchische Zustandsmaschine ist (der Code aus dem Standardbeispiel lautet 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 ); } };
Wir haben dies alles bereits
in einem separaten Artikel beschrieben , es besteht keine Notwendigkeit, es zu wiederholen.
Orthogonale Zustände werden derzeit nicht unterstützt. Diese Tatsache hat jedoch zwei Erklärungen. Erstens haben wir versucht, diese Unterstützung zu leisten, und waren mit einer Reihe von Schwierigkeiten konfrontiert, deren Überwindung uns zu kostspielig erschien. Und zweitens hat noch niemand nach orthogonalen Zuständen gefragt. Wenn Sie gefragt werden, kehren Sie zu diesem Thema zurück.
Welchen Einfluss hatte es?
Es gibt das Gefühl, dass es sehr ernst ist (obwohl wir hier natürlich subjektiv und voreingenommen sind). Schließlich ist es eine Sache, wenn Sie angesichts komplexer Finite-State-Maschinen im Themenbereich nach Problemumgehungen suchen, etwas vereinfachen und zusätzliche Kraft für etwas ausgeben. Ganz anders ist es, wenn Sie Objekte aus Ihrer Anwendung fast 1: 1 Ihrem C ++ - Code zuordnen können.
Darüber hinaus wird diese Funktionalität verwendet, gemessen an den Fragen, die beispielsweise nach dem Verhalten von Eingabe- / Ausgabe-Handlern innerhalb / außerhalb des Status gestellt werden.
Mchain's
Was war?
Es war eine interessante Situation. SObjectizer wurde häufig verwendet, sodass nur ein Teil der Anwendung in SObjectizer geschrieben wurde. Der Rest des Codes in der Anwendung hat möglicherweise nichts mit Akteuren im Allgemeinen oder mit SObjectizer im Besonderen zu tun. Zum Beispiel eine GUI-Anwendung, in der ein SObjectizer für einige Hintergrundaufgaben verwendet wird, während die Hauptarbeit am Hauptthread der Anwendung ausgeführt wird.
In solchen Fällen stellte sich heraus, dass das Senden von Informationen vom Nicht-SObjectizer-Teil zum SObjectizer-Teil so einfach wie einfach ist: Es reicht aus, normale Sendefunktionen aufzurufen. Die Verbreitung von Informationen in die entgegengesetzte Richtung ist jedoch nicht so einfach. Es schien uns, dass dies nicht gut ist und dass Sie einige bequeme Kommunikationskanäle zwischen den SObjectizer-Teilen der Anwendung und Nicht-SObjectizer-Teilen direkt nach dem Auspacken haben sollten.
Was ist geworden?
In SObjectizer wurden also Nachrichtenketten oder, in der bekannteren Notation, Ketten angezeigt.
Mchain ist eine solche spezielle Variante eines Einzelverbraucher-Postfachs, in dem Nachrichten über reguläre Sendefunktionen gesendet werden. Um Nachrichten aus mchain zu extrahieren, müssen Sie jedoch keine Agenten erstellen und signieren. Es gibt zwei spezielle Funktionen, die auch innerhalb von Agenten und sogar außerhalb von Agenten aufgerufen werden können: receive () und select (). Der erste liest Nachrichten von nur einem Kanal, während der zweite Nachrichten von mehreren Kanälen gleichzeitig lesen kann:
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>){...}, ... ));
Wir haben hier bereits mehrmals über mchain gesprochen:
im August 2017 und
im Mai 2018 . Daher werden wir hier nicht näher darauf eingehen, insbesondere was das Aussehen der Arbeit mit Ketten betrifft.
Welchen Einfluss hatte es?
Nach dem Erscheinen von mchains in SObjectizer-5.5 stellte sich heraus, dass SObjectizer tatsächlich noch weniger zu einem „Akteur“ -Framework wurde als zuvor. SObjectizer unterstützte nicht nur das Actor Model und Pub / Sub, sondern auch das CSP-Modell (Communicating Sequential Processes). Mit Mchains können Sie ziemlich komplexe Multithread-Anwendungen auf SObjectizer ohne Akteure entwickeln. Und für einige Aufgaben ist dies mehr als bequem. Was wir selbst von Zeit zu Zeit verwenden.
Mechanismus zur Begrenzung von Nachrichten
Was war?
Einer der schwerwiegendsten Mängel des Modells der Akteure ist die Veranlagung zum Auftreten von Überlastungen. Es ist sehr einfach, sich in einer Situation zu befinden, in der der sendende Akteur Nachrichten schneller an den empfangenden Akteur sendet, als der empfangende Akteur die Nachrichten verarbeiten kann.
Das Senden von Nachrichten in Actor Frameworks ist in der Regel eine nicht blockierende Operation. Wenn daher ein Paar aus "flinkem Produzenten und nerdigem Konsumenten" auftritt, erhöht sich die Warteschlange des Empfängerakteurs, während mindestens noch freier Speicherplatz vorhanden ist.
Die Hauptschwierigkeit dieses Problems besteht darin, dass ein guter Mechanismus zum Schutz vor Überlastung für die angewandte Aufgabe und die Merkmale des Themenbereichs geschärft werden sollte. Zum Beispiel, um zu verstehen, welche Nachrichten dupliziert werden können (und daher Duplikate sicher verwerfen können). Um zu verstehen, welche Nachrichten sowieso nicht weggeworfen werden können. Wer kann um wie viel suspendiert werden und wer darf das überhaupt nicht. Usw. usw.
Eine weitere Schwierigkeit besteht darin, dass ein guter Abwehrmechanismus nicht immer erforderlich ist. Manchmal reicht es aus, etwas Primitives, aber Effektives zu haben, das sofort einsatzbereit und einfach zu bedienen ist.
Um den Benutzer nicht zu zwingen, seine Überlastungskontrolle dort durchzuführen, wo es ausreicht, einfach "zusätzliche" Nachrichten zu verwerfen oder diese Nachrichten an einen anderen Agenten weiterzuleiten.Was ist geworden?
Nur damit Sie in einfachen Szenarien vorgefertigte Überlastschutz-Tools verwenden können, die sogenannten Nachrichtenlimits. Mit diesem Mechanismus können Sie unnötige Nachrichten verwerfen oder an andere Empfänger senden oder die Anwendung einfach unterbrechen, wenn die Grenzwerte überschritten werden. Zum Beispiel: class worker : public so_5::agent_t { public: worker(context_t ctx) : so_5::agent_t{ ctx
Dieses Thema wird in einem separaten Artikel ausführlicher beschrieben .Welchen Einfluss hatte es?
Dies bedeutet nicht, dass die Entstehung von Nachrichtengrenzen zu etwas geworden ist, das SObjectizer, die Prinzipien seiner Arbeit oder die Arbeit damit grundlegend verändert hat. Vielmehr kann es mit einem Reservefallschirm verglichen werden, der nur als letztes Mittel eingesetzt wird. Aber wenn Sie es benutzen müssen, sind Sie froh, dass es überhaupt existiert.Mechanismus zur Verfolgung der Nachrichtenübermittlung
Was war?
SObjectizer-5 war eine Black Box für Entwickler. In dem die Nachricht gesendet wird und ... Und sie kommt entweder zum Empfänger oder kommt nicht.Wenn die Nachricht den Empfänger nicht erreicht, muss der Benutzer auf der Suche nach einem Grund eine spannende Suche durchlaufen. In den meisten Fällen sind die Gründe trivial: Entweder wurde die Nachricht an die falsche mbox gesendet oder das Abonnement wurde nicht abgeschlossen (z. B. hat der Benutzer in einem Status des Agenten ein Abonnement abgeschlossen, in einem anderen Status jedoch vergessen). Es kann jedoch komplexere Fälle geben, in denen eine Nachricht beispielsweise von einem Überlastschutzmechanismus abgelehnt wird.Das Problem bestand darin, dass der Mechanismus zur Nachrichtenübermittlung tief in den Innereien der SObjectizer-Laufzeit verborgen war und es daher selbst für die Entwickler von SObjectizer schwierig war, die Nachricht an den Empfänger weiterzuleiten, ganz zu schweigen von den Benutzern. Besonders über Anfänger, die die meisten dieser trivialen Fehler gemacht haben.Was ist geworden?
In SObjectizer-5.5 wurde ein spezieller Mechanismus zum Verfolgen des Nachrichtenübermittlungsprozesses hinzugefügt, der als Nachrichtenübermittlungsverfolgung (oder einfach als msg_tracing) bezeichnet wird, und anschließend abgeschlossen. Dieser Mechanismus und seine Funktionen wurden in einem separaten Artikel ausführlicher beschrieben .Wenn Nachrichten bei der Zustellung verloren gehen, können Sie einfach msg_tracing aktivieren und sehen, warum dies geschieht.Welchen Einfluss hatte es?
Das Debuggen von in SObjectizer geschriebenen Anwendungen ist viel einfacher und unterhaltsamer geworden. Auch für uns .Das Konzept von env_infrastructure und Single-Threaded-env_infrastructure
Was war?
Wir haben SObjectizer immer als Werkzeug betrachtet, um die Entwicklung von Multithread-Code zu vereinfachen. Daher wurden die ersten Versionen von SObjectizer-5 so geschrieben, dass sie nur in einer Multithread-Umgebung funktionieren.Dies wurde als Verwendung von Synchronisationsprimitiven im SObjectizer zum Schutz der Interna des SObjectizers beim Arbeiten in einer Multithread-Umgebung ausgedrückt. Es geht also darum, mehrere zusätzliche Arbeitsthreads im SObjectizer selbst zu erstellen (um so wichtige Vorgänge wie das Warten des Timers und das Abschließen der Abmeldung von Agentenkooperationen durchzuführen).Das heißt,
SObjectizer wurde für die Multithread-Programmierung und für die Verwendung in Multithread-Umgebungen entwickelt. Und das hat uns perfekt gepasst.Da der SObjectizer jedoch "in the wild" verwendet wurde, wurden Situationen entdeckt, in denen die Aufgabe schwierig genug war, um Akteure in ihrer Lösung zu verwenden. Gleichzeitig konnten und mussten alle Arbeiten in einem einzigen Arbeitsablauf ausgeführt werden.Und wir hatten ein sehr interessantes Problem: Ist es möglich, SObjectizer beizubringen, an einem einzelnen Arbeitsthread zu arbeiten?Was ist geworden?
Es stellte sich heraus, dass es möglich ist.Es hat uns viel Geld gekostet, es hat viel Zeit und Mühe gekostet, eine Lösung zu finden. Aber die Lösung wurde erfunden.Ein Konzept wie die Umgebungsinfrastruktur wurde eingeführt (oder env_infrastructure in leicht abgekürzter Form). Env_infrastructure übernahm die Verwaltung der internen SObjectizer-Küche. Insbesondere löste er Probleme wie die Wartung von Zeitgebern, die Registrierung und die Abmeldung von Genossenschaften.Für SObjectizer wurden mehrere Single-Thread-Optionen für env_infrastructures festgelegt. Auf diese Weise konnten wir Single-Threaded-Anwendungen auf SObjectizer entwickeln, in denen sich normale Agenten befinden, die regelmäßig Nachrichten miteinander austauschen.Wir haben in einem separaten Artikel ausführlicher auf diese Funktionalität eingegangen. .Welchen Einfluss hatte es?
Das vielleicht wichtigste Ereignis bei der Implementierung dieser Funktion war das Aufbrechen unserer eigenen Vorlagen. Ein Blick auf SObjectizer wird niemals derselbe sein. Betrachten Sie SObjectizer seit so vielen Jahren ausschließlich als Werkzeug für die Entwicklung von Multithread-Code. Und dann wieder! Und finden Sie heraus, dass Single-Threaded-Code auf SObjectizer auch entwickelt werden kann. Das Leben ist voller Überraschungen.Tools zur Laufzeitüberwachung
Was war?
SObjectizer-5 war nicht nur hinsichtlich des Nachrichtenübermittlungsmechanismus eine Black Box. Es gab jedoch auch keine Möglichkeit herauszufinden, wie viele Agenten derzeit in der Anwendung arbeiten, wie viele und welche Dispatcher erstellt werden, wie viele Arbeitsthreads beteiligt sind, wie viele Nachrichten in den Warteschlangen der Dispatcher warten usw.All diese Informationen sind sehr nützlich für die Überwachung von Anwendungen, die rund um die Uhr ausgeführt werden. Aber zum Debuggen möchte ich auch von Zeit zu Zeit verstehen, ob die Warteschlangen wachsen oder die Anzahl der Agenten zunimmt / abnimmt.Leider haben unsere Hände vorerst einfach nicht den Punkt erreicht, SObjectizer Mittel für das Sammeln und Verbreiten solcher Informationen hinzuzufügen.Was ist geworden?
Zu einem bestimmten Zeitpunkt in SObjectizer-5.5 wurden Tools zur Laufzeitüberwachung der Interna von SObjectizer angezeigt . Standardmäßig ist die Laufzeitüberwachung deaktiviert. Wenn Sie sie jedoch aktivieren, werden regelmäßig Nachrichten an eine spezielle mbox gesendet, in der Informationen zur Anzahl der Agenten und Kooperationen, zur Anzahl der Zeitgeber, zu Arbeitsthreads von Dispatchern (und zu denen bereits Informationen vorhanden sind) angezeigt werden die Anzahl der Nachrichten in den Warteschlangen, die Anzahl der Agenten, die an diese Threads gebunden sind).Darüber hinaus wurde es im Laufe der Zeit möglich, zusätzlich die Erfassung von Informationen darüber zu ermöglichen, wie viel Zeit Agenten in Ereignishandlern verbringen. Auf diese Weise können Sie Situationen erkennen, in denen ein Agent zu langsam ist (oder Zeit für das Blockieren von Anrufen verschwendet).Welchen Einfluss hatte es?
In unserer Praxis wird die Laufzeitüberwachung nicht oft verwendet. Aber wenn Sie es brauchen, dann erkennen Sie seine Bedeutung. In der Tat ist es ohne einen solchen Mechanismus unmöglich (gut oder sehr schwierig) herauszufinden, was und wie nicht funktioniert.Dies ist also eine Funktion aus der Kategorie „Sie können es tun“, aber ihre Präsenz überträgt das Instrument unserer Meinung nach sofort in eine andere Gewichtsklasse. Weil
Es ist nicht so schwierig, einen Prototyp eines Schauspieler-Frameworks "auf dem Knie" zu erstellen. Viele haben dies getan und viele weitere werden es tun. Aber dann, um Ihre Entwicklung mit einer Laufzeitüberwachung auszustatten ... Inzwischen überleben nicht alle von Knien gezogenen Entwürfe.Und noch etwas in einer Zeile
Seit vier Jahren hat SObjectizer-5.5 viele Neuerungen und Änderungen erfahren, deren Beschreibung selbst in einer Zusammenfassung zu viel Platz beanspruchen wird. Daher bezeichnen wir einen Teil von ihnen buchstäblich mit einer Zeile. In zufälliger Reihenfolge ohne Prioritäten.SObjectizer-5.5 bietet Unterstützung für das CMake-Build-System.Jetzt kann SObjectizer-5 sowohl als dynamische als auch als statische Bibliothek erstellt werden.SObjectizer-5.5 wurde jetzt erstellt und läuft auf Android (sowohl über CrystaX NDK als auch über neues Android NDK).Private Dispatcher sind erschienen. Jetzt können Sie Dispatcher erstellen und verwenden, die sonst niemand sieht.Implementierung des Übermittlungsfiltermechanismus. Wenn Sie jetzt Nachrichten von MPMC-mboxes abonnieren, können Sie die Zustellung von Nachrichten verbieten, deren Inhalt Sie nicht interessiert.Die Tools zum Erstellen und Registrieren von Kooperationen wurden erheblich vereinfacht: Die Methoden Introduce_coop / Introduce_child_coop, Make_agent / Make_agent_with_binder und das ist alles.Das Konzept einer Fabrik von Sperrobjekten wurde angezeigt. Jetzt können Sie auswählen, welche Sperrobjekte Sie benötigen (basierend auf Mutex, Spinlock, kombiniert oder einem anderen).Die Klasse wrap_env_t wurde angezeigt und jetzt können Sie SObjectizer in Ihrer Anwendung nicht nur mit so_5 :: launch () ausführen.Das Konzept von stop_guards wurde angezeigt und jetzt können Sie den Shutdown-Prozess von SObjectizer beeinflussen. Sie können beispielsweise verhindern, dass SObjectizer gestoppt wird, bis einige Agenten ihre Anwendungsarbeit abgeschlossen haben.Jetzt können Sie Nachrichten abfangen, die an den Agenten übermittelt, aber nicht vom Agenten verarbeitet wurden (die sogenannten dead_letter_handlers).Es gab die Möglichkeit, Nachrichten in spezielle "Umschläge" zu verpacken. Umschläge können zusätzliche Informationen über die Nachricht enthalten und eine Aktion ausführen, wenn die Nachricht an den Empfänger übermittelt wird.5.5.0 bis 5.5.23 in Zahlen
Es ist auch interessant, den Pfad in Bezug auf Code / Tests / Beispiele zu betrachten. Das Cloc-Dienstprogramm gibt folgende Informationen zur Menge des SObjectizer-5.5.0-Kernel-Codes an: -------------------------------------------------- -----------------------------
Sprachdateien leerer Kommentarcode
-------------------------------------------------- -----------------------------
C / C ++ - Header 58 2119 5156 5762
C ++ 39 1167 779 4759
Ruby 2 30 2 75
-------------------------------------------------- -----------------------------
SUMME: 99 3316 5937 10596
-------------------------------------------------- -----------------------------
Und hier ist das Gleiche, aber für v.5.5.23 (von denen 1147 Zeilen der Code der optional-lite-Bibliothek sind): -------------------------------------------------- -----------------------------
Sprachdateien leerer Kommentarcode
-------------------------------------------------- -----------------------------
C / C ++ - Header 133 6279 22173 21068
C ++ 53 2498 2760 10398
CMake 2 29 0 177
Ruby 4 53 2 129
-------------------------------------------------- -----------------------------
SUM: 192 8859 24935 31772
-------------------------------------------------- -----------------------------
Testvolumen für v.5.5.0: -------------------------------------------------- -----------------------------
Sprachdateien leerer Kommentarcode
-------------------------------------------------- -----------------------------
C ++ 84 2510 390 11540
Ruby 162 496 0 1054
C / C ++ - Header 1 11 0 32
-------------------------------------------------- -----------------------------
SUMME: 247 3017 390 12626
-------------------------------------------------- -----------------------------
Tests für v.5.5.23: -------------------------------------------------- -----------------------------
Sprachdateien leerer Kommentarcode
-------------------------------------------------- -----------------------------
C ++ 324 7345 1305 35231
Ruby 675 2,353 0 4,671
CMake 338 43 0 955
C / C ++ - Header 11 107 3 448
-------------------------------------------------- -----------------------------
SUMME: 1348 9848 1308 41305
Nun, Beispiele für v.5.5.0: -------------------------------------------------- -----------------------------
Sprachdateien leerer Kommentarcode
-------------------------------------------------- -----------------------------
C ++ 27 765 463 3322
Ruby 28 95 0 192
-------------------------------------------------- -----------------------------
SUMME: 55 860 463 3514
Sie sind, aber schon für v.5.5.23: -------------------------------------------------- -----------------------------
Sprachdateien leerer Kommentarcode
-------------------------------------------------- -----------------------------
C ++ 67 2141 2061 9341
Ruby 133 451 0 868
CMake 67 93 0 595
C / C ++ - Header 1 12 11 32
-------------------------------------------------- -----------------------------
SUMME: 268 2697 2072 10836
Fast überall eine fast dreifache Steigerung.Und der Umfang der Dokumentation für SObjectizer hat wahrscheinlich noch mehr zugenommen.Pläne für die nahe (und nicht nur) Zukunft
Vor etwa einem Monat wurden hier vorläufige Entwicklungspläne für SObjectizer nach der Veröffentlichung von Version 5.5.23 beschrieben . Grundsätzlich haben sie sich nicht geändert. Es bestand jedoch das Gefühl, dass Version 5.6.0, deren Veröffentlichung für Anfang 2019 geplant ist, als Beginn des nächsten stabilen Zweigs von SObjectizer positioniert werden muss. Mit Blick auf die Tatsache, dass sich SObjectizer im Jahr 2019 innerhalb des 5.6-Zweigs entwickeln wird, ohne dass wesentliche Änderungen vorgenommen werden.Auf diese Weise können diejenigen, die jetzt SO-5.5 in ihren Projekten verwenden, schrittweise auf SO-5.6 umsteigen, ohne befürchten zu müssen, dass sie ebenfalls auf SO-5.7 umsteigen müssen.Die Version 5.7, in der wir uns erlauben wollen, irgendwo von den Grundprinzipien von SO-5.5 und SO-5.6 abzuweichen, wird 2019 als experimentell betrachtet. Mit Stabilisierung und Freigabe, wenn alles gut geht, schon im 2020. Jahr.Fazit
Abschließend möchte ich mich bei allen bedanken, die uns die ganze Zeit bei der Entwicklung von SObjectizer geholfen haben. Und ich möchte mich separat bei allen bedanken, die es gewagt haben, mit SObjectizer zu arbeiten. Ihr Feedback war für uns immer sehr nützlich.Wir möchten denjenigen sagen, die SObjectizer noch nicht verwendet haben: Probieren Sie es aus. Dies ist nicht so beängstigend, wie es scheinen mag.Wenn Ihnen etwas nicht gefallen hat oder Sie in SObjectizer nicht genug hatten, teilen Sie uns dies mit. Wir hören immer auf konstruktive Kritik. Und wenn es in unserer Macht steht, erwecken wir die Wünsche der Benutzer zum Leben.