Was ist neu in SObjectizer-5.7.0 und worauf wartet dieses Projekt als nächstes?

SObjectizer ist ein relativ kleines C ++ 17-Framework, mit dem Sie Ansätze wie Actor Model, Publish-Subscribe und Communicating Sequential Processes (CSP) in C ++ - Programmen verwenden können. Dies vereinfacht die Entwicklung komplexer Multithread-Anwendungen in C ++ erheblich. Wenn der Leser zum ersten Mal von SObjectizer hört, können Sie sich anhand dieser Präsentation oder anhand dieses bereits recht alten Artikels ein Bild von ihm machen.


Generell gibt es nicht so viele ähnliche offene, noch lebende und sich noch entwickelnde Tools für C ++. Man kann sich nur an QP / C ++ , CAF: C ++ Actor Framework , actor-zeta und das sehr junge Rotorprojekt erinnern. Es gibt eine Wahl, aber nicht so groß.


In letzter Zeit ist eine weitere "Major" -Version von SObjectizer verfügbar geworden, in der endlich etwas aufgetaucht ist, worüber schon lange gesprochen wurde und auf das ich mehrmals erfolglos zugegriffen habe. Wir können sagen, dass ein Meilenstein erreicht wurde. Dies ist auch eine Gelegenheit darüber zu sprechen, was SObjectizer nach der Veröffentlichung von Version 5.7.0 erwartet.


Send_case-Unterstützung in select ()


Die wichtigste Neuerung in Version 5.7.0, für die die Kompatibilität mit Version 5.6 im letzten Jahr veröffentlicht wurde (und die Kompatibilität wird nicht beeinträchtigt), war die fehlerhafte send_case-Unterstützung in der select () -Funktion. Was hat SObjectizers select () viel ähnlicher gemacht als Go selects. Mit select () können Sie jetzt nicht nur Nachrichten von mehreren CSP-Kanälen lesen, sondern auch ausgehende Nachrichten an die Kanäle senden, die zum Schreiben bereit waren.


Aber um dieses Thema zu enthüllen, müssen Sie von weitem beginnen.


Die Entstehung von CSP-Elementen in SObjectizer-5


In SObjectizer-5 tauchten Elemente von CSP auf, nämlich Analoga von CSP-Kanälen, um nicht das Kontrollkästchen "CSP-Unterstützung" zu aktivieren, sondern ein praktisches Problem zu lösen.


Die Sache war, dass, wenn die gesamte Anwendung vollständig auf SObjectizer basiert, der Informationsaustausch zwischen verschiedenen Entitäten (Teilen) des Programms auf einzig offensichtliche Weise realisiert wird. Alles in der Anwendung wird in Form von Agenten (Akteuren) dargestellt, und Agenten senden sich einfach auf standardmäßige Weise Nachrichten.


Aber wenn in der Anwendung nur ein Teil der Funktionalität auf SObjectizer implementiert ist ...


Zum Beispiel eine GUI-Anwendung auf Qt oder wxWidgets, in der der Hauptteil des Codes eine GUI ist, und ein SObjectizer, um einige Hintergrundaufgaben auszuführen. Oder ein Teil der Anwendung wird mit Bare-Threads und Asio geschrieben, und von Asio gelesene Daten aus dem Netzwerk werden zur Verarbeitung an SObjectizer-Agenten gesendet.


Wenn eine Anwendung einen SObjectizer-Teil und einen Nicht-SObjectizer-Teil hat, stellt sich die Frage: Wie werden Informationen vom SObjectizer-Teil der Anwendung zum Nicht-SObjectizer-Teil übertragen?


Die Lösung wurde in Form von sogenannten gefunden Nachrichtenketten (mchains), d.h. Gespräche. Was sich zufällig als die Essenz der CSP-Kanäle herausstellte. Der SObjectizer-Teil der Anwendung sendet unter Verwendung der regulären send () -Funktion auf die übliche Weise Nachrichten an mchain.


Um Nachrichten vom Nicht-SObjectizer-Teil zu lesen, können Sie die neue Funktion receive () verwenden, mit der Sie keine Agenten erstellen oder in andere Bereiche von SObjectizer eintauchen müssen.


Es stellte sich als ziemlich verständliches und funktionierendes Schema heraus.


Missbrauch von Ketten


Darüber hinaus erwies sich das Schema als so verständlich und funktionierte so schnell, dass einige Anwendungen auf SObjectizer überhaupt ohne Agenten zu schreiben begannen, nur auf mchain-ahs. Das heißt mit dem CSP-Ansatz, nicht das Actor Model. Hier auf Habré gab es bereits Artikel darüber: eins und zwei .


Dies führte zu zwei interessanten Konsequenzen.


Erstens ist die Funktion receive () mit erweiterten Funktionen überladen. Dies war erforderlich, damit nur ein Aufruf von receive () möglich war, dessen Rückkehr erfolgen würde, wenn alle erforderlichen Arbeiten bereits ausgeführt wurden. Hier sind Beispiele dafür, was SObjectizer mit receive () tun kann:


using namespace so_5; //    3 . //  3    mchain ,   . //   receive    3 , //      . receive( from(chain).handle_n( 3 ), handlers... ); //    3 . //       mchain ,    //     200ms. // ..     200ms,    receive,   //      . receive( from(chain).handle_n( 3 ).empty_timeout( milliseconds(200) ), handlers... ); //       . //     ,    //  500ms. receive( from(chain).handle_all().empty_timeout( milliseconds(500) ), handlers... ); //       . //       2s. receive( from(chain).handle_all().total_time( seconds(2) ), handlers... ); 

Zweitens wurde schnell klar, dass obwohl verschiedene Arten von Nachrichten in der SObjectizer-Kette platziert werden können und trotz der erweiterten Funktion receive () manchmal mit mehreren Kanälen gleichzeitig gearbeitet werden muss ...


Wähle (), aber schreibgeschützt


Die Funktion select () wurde zu SObjectizer hinzugefügt, um Nachrichten von mehreren Ketten zu lesen und zu verarbeiten. Clear Business Select () erschien nicht nur so, sondern unter dem Einfluss der Go-Sprache. Aber SObjectizers select () hatte zwei Funktionen.


Erstens war unser select () wie receive () skriptorientiert, wenn select () nur einmal aufgerufen wird und die gesamte nützliche Arbeit darin erledigt ist. Zum Beispiel:


 using namespace so_5; mchain_t ch1 = env.create_mchain(...); mchain_t ch2 = env.create_mchain(...); //    3 . //    3    ch1. //  2  ch1    ch2. //    ch1  2  ch2... // //   ,       . // select()      3 , //     . select( from_all().handle_n( 3 ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, []( so_5::mhood_t< some_signal_type > ) { ... } ), ... ) ); //    3 . //    ,     200ms. select( from_all().handle_n( 3 ).empty_timeout( milliseconds(200) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, []( so_5::mhood_t< some_signal_type > ) { ... } ), ... ) ); //       . //    ,     500ms. select( from_all().handle_all().empty_timeout( milliseconds(500) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, []( so_5::mhood_t< some_signal_type > ) { ... } ), ... ) ); 

Zweitens unterstützte select () das Senden von Nachrichten an den Kanal nicht. Das heißt Nachrichten von Kanälen konnten gelesen werden. Um jedoch Nachrichten an den Kanal zu senden, wählen Sie () - Nr.


Jetzt ist es sogar schwer, sich daran zu erinnern, warum es passiert ist. Wahrscheinlich, weil select () mit send_case-Unterstützung eine schwierige Aufgabe war und keine Ressourcen gefunden wurden, um sie zu lösen.


M-Ketten in SObjectizer sind schwieriger als Kanäle in Go


Zunächst wurde select () ohne send_case-Unterstützung nicht als Problem angesehen. Tatsache ist, dass Ketten in SObjectizer ihre eigenen Besonderheiten haben, die Go-Kanäle nicht haben.


Erstens werden SObjectizer-Ketten in dimensionslose und mit einer festen maximalen Kapazität unterteilt. Wenn daher send () für eine dimensionslose Kette ausgeführt wird, wird dieses send () im Prinzip nicht blockiert. Daher ist es nicht sinnvoll, select () zu verwenden, um eine Nachricht an die dimensionslose Kette zu senden.


Zweitens wird bei Ketten mit fester maximaler Kapazität beim Erstellen sofort angezeigt, was passiert, wenn Sie versuchen, eine Nachricht an die vollständige Kette zu schreiben:


  • Muss ich auf freien Speicherplatz in mchain warten? Und wenn nötig, wie lange?
  • Wenn es keinen freien Speicherplatz gibt, was tun? Löschen Sie die älteste Nachricht aus der mchain, ignorieren Sie die neue Nachricht, lösen Sie eine Ausnahme aus oder rufen Sie std :: abort () auf (dieses harte Skript ist in der Praxis sehr gefragt).

Daher war in SObjectizer ein (meines Wissens) recht häufiges Szenario, in Go select zu verwenden, um eine Nachricht zu senden, die Goroutin nicht fest blockiert, sofort in SObjectizer ohne Funken und ohne select verfügbar.


Am Ende eine vollständige Auswahl ()


Trotzdem verging die Zeit, gelegentlich gab es Fälle, in denen der Mangel an send_case-Unterstützung in select () immer noch betroffen war. Darüber hinaus haben in diesen Fällen die eingebauten Fähigkeiten von Ketten nicht geholfen, sondern eher das Gegenteil.


Daher habe ich von Zeit zu Zeit versucht, mich dem Problem der send_case-Implementierung zu nähern. Aber bis vor kurzem hat nichts funktioniert. Hauptsächlich, weil es nicht möglich war, das Design dieses send_case selbst zu entwerfen. Das heißt Wie soll send_case in select () aussehen? Was genau soll er tun, wenn es möglich ist zu senden? Im Falle der Unmöglichkeit? Was tun mit der Unterteilung in dimensionslose und feste Ketten?


Auf diese und andere Fragen konnte ich erst im Dezember 2019 Antworten finden. Vor allem aufgrund von Konsultationen mit Personen, die mit Go vertraut sind und Go Selects in der Praxis eingesetzt haben. Nun, sobald das send_case-Bild endlich Gestalt angenommen hatte, traf die Implementierung genau dort ein.


So, jetzt kannst du so schreiben:


 using namespace so_5; struct Greeting { std::string text_; }; select(from_all().handle_n(1), send_case(ch, message_holder_t<Greeting>::make("Hello!"), []{ std::cout << "Hello sent!" << std::endl; })); 

Wichtig ist, dass send_case in select () die Überladungsantwort ignoriert, die für die Ziel-Mchain festgelegt wurde. Im obigen Beispiel könnte ch also mit der Reaktion abort_app erstellt werden, wenn versucht wird, eine Nachricht an den vollständigen Kanal zu senden. Und wenn Sie versuchen, simple send () aufzurufen, um in ch zu schreiben, kann std :: abort () aufgerufen werden. Aber im Fall von select () - und dies wird nicht passieren, wird select () warten, bis freier Speicherplatz in ch erscheint. Oder bis ch geschlossen ist.


Hier einige weitere Beispiele, wie send_case in select () von SObjectizer vorgehen kann:


 using namespace so_5; //     ,   //    . //    . select(from_all().handle_n(1), send_case(ch1, message_holder_t<FirstMessage>::make(...), []{...}), send_case(ch2, message_holder_t<SecondMessage>::make(...), []{...}), send_case(ch3, message_holder_t<ThirdMessage>::make(...), []{...})); //     . //     ( ) //   ( ). select(from_all().handle_n(3), send_case(ch1, message_holder_t<FirstMessage>::make(...), []{...}), send_case(ch2, message_holder_t<SecondMessage>::make(...), []{...}), send_case(ch3, message_holder_t<ThirdMessage>::make(...), []{...})); //     chW. //     chW    150ms. select(from_all().handle_n(1).empty_timeout(150ms), send_case(chW, message_holder_t<Msg>::make(...), []{...})); //     chW. //  ,   chW   . select(from_all().handle_n(1).no_wait_on_empty(), send_case(chW, message_holder_t<Msg>::make(...), []{...})); //    ,      250ms. select(from_all().handle_all().total_time(250ms), send_case(ch1, message_holder_t<FirstMessage>::make(...), []{...}), send_case(ch2, message_holder_t<SecondMessage>::make(...), []{...}), send_case(ch3, message_holder_t<ThirdMessage>::make(...), []{...})); 

Natürlich kann send_case in select () in Verbindung mit receive_case verwendet werden:


 //          //  .       //  . select(from_all().handle_n(1), send_case(ch1, message_holder_t<FirstMsg>::make(...), []{...}), send_case(ch2, message_holder_t<SecondMsg>::make(...), []{...}), receive_case(ch3, [](...){...}), receive_case(ch4, [](...){...})); 

So kann jetzt in SObjectizer der CSP-Ansatz in allen Bereichen verwendet werden. Es wird nicht schlimmer sein als in Go. Natürlich ausführlich. Aber nicht schlimmer :)


Wir können sagen, dass die lange Geschichte des Hinzufügens von Unterstützung für den CSP-Ansatz zu SObjectizer beendet ist.


Andere wichtige Dinge in dieser Version


Letzter Umzug nach Github


SObjectizer lebte und entwickelte ursprünglich bei SourceForge . Ein Jahr der Werbung seit 2006. Bei SF.net ging die Leistung von Subversion jedoch immer weiter zurück, sodass wir letztes Jahr zu BitBucket und Mercurial wechselten. Sobald wir dies getan haben, gab Atlassian bekannt, dass Mercurial-Repositorys mit BitBucket bald vollständig gelöscht werden. Daher befinden sich seit August 2019 sowohl SObjectizer als auch so5extra auf GitHub.


SF.net hat den gesamten alten Inhalt, einschließlich des Wikis mit der Dokumentation für frühere Versionen von SObjectizer. Außerdem können Sie im Bereich "Dateien" Archive verschiedener Versionen von SObjectizer / so5extra herunterladen (z. B. PDF-Dateien mit einigen Präsentationen zu SObjectizer ).


Im Allgemeinen suchen Sie uns jetzt auf GitHub . Und vergessen Sie nicht, Sterne zu setzen, wir haben vorerst zu wenig;)


Behobenes Verhalten von umhüllten Nachrichten


In SO-5.7.0 fand eine kleine Korrektur statt, die nicht erwähnt werden konnte. Es ist jedoch zu erwähnen, dass dies eine gute Demonstration dafür ist, wie sich die verschiedenen Funktionen von SObjectizer während der Entwicklung gegenseitig beeinflussen.


Vor vier Jahren wurde SObjectizer um die Unterstützung für Agenten erweitert, die hierarchische Zustandsautomaten sind (weitere Details hier ). Nach einigen Jahren wurden dann Nachrichtenumschläge zu SObjectizer hinzugefügt. Das heißt Die Nachricht wurde beim Senden in ein zusätzliches Umschlagobjekt eingeschlossen, und dieser Umschlag konnte Informationen darüber erhalten, was mit der Nachricht geschieht.


Eines der Merkmale des Mechanismus für umhüllte Nachrichten besteht darin, dass dem Umschlag mitgeteilt wird, dass die Nachricht an den Adressaten zugestellt wurde. Das heißt, dass ein Handler für diese Nachricht beim Teilnehmeragenten gefunden wurde und dieser Handler aufgerufen wurde.


Es stellte sich heraus, dass, wenn der Empfängeragent der Nachricht eine hierarchische Zustandsmaschine ist, die eine Funktion wie suppress() (d. H. Das Erzwingen, dass die Nachricht in einem bestimmten Zustand ignoriert wird), der Umschlag möglicherweise eine Benachrichtigung über eine falsche Zustellung erhält, obwohl die Nachricht tatsächlich vom Empfänger zurückgewiesen wurde aufgrund von suppress() . Eine noch interessantere Situation war mit transfer_to_state() , weil Nach dem Ändern des Status des empfangenden Agenten wird der Nachrichtenhandler möglicherweise gefunden oder fehlt. Aber der Umschlag über die Zustellung der Nachricht wurde trotzdem informiert.


Sehr seltene Fälle, die meines Wissens von niemandem in der Praxis nachgewiesen wurden. Trotzdem wurde eine Fehleinschätzung vorgenommen.


Daher wurde in SO-5.7.0 dieser Punkt verbessert. Wenn die Nachricht infolge der Anwendung von suppress() oder transfer_to_state() ignoriert wird, glaubt der Umschlag nicht mehr, dass die Nachricht an den Adressaten zugestellt wurde.


Zusätzliche so5extra-Bibliothek ändert die BSD-3-CLAUSE-Lizenz


2017 haben wir begonnen, eine Bibliothek mit zusätzlichen Komponenten für SObjectizer namens so5extra zu erstellen . In dieser Zeit ist die Bibliothek erheblich gewachsen und enthält viele nützliche Dinge im Haushalt.


So5extra wurde ursprünglich unter einer doppelten Lizenz vertrieben: GNU Affero GPL v.3 für Open Source-Projekte und kommerzielle für geschlossene Projekte.


Jetzt haben wir die Lizenz für so5extra geändert und ab Version 1.4.0 wird so5extra unter der BSD-3-CLAUSE-Lizenz vertrieben. Das heißt Es kann kostenlos verwendet werden, auch wenn proprietäre Software entwickelt wird.


Wenn Sie also etwas in SObjectizer vermissen, können Sie sich so5extra ansehen . Was ist, wenn Sie bereits das haben, was Sie brauchen?


Die Zukunft von SObjectizer


Bevor Sie ein paar Worte darüber sagen, worauf SObjectizer wartet, ist ein wichtiger Exkurs erforderlich. Speziell für diejenigen, die glauben, dass SObjectizer eine "Referenzverschwendung" ist, "kniehohe Verarbeitung", "Schülerlabor", "experimentelle Projektion, die die Autoren aufgeben, wenn sie genug spielen" ... (dies ist nur ein Teil der Eigenschaften, die wir von Experten gehört haben unseres Internets in den letzten 4-5 Jahren).


Ich habe SObjectizer für fast achtzehn Jahre entwickelt. Und ich kann verantwortungsbewusst sagen, dass er nie ein Pilotprojekt war. Dies ist ein praktisches Werkzeug, das seit seiner allerersten Version im Jahr 2002 in die Praxis umgesetzt wurde.


Sowohl ich als auch meine Kollegen und Leute, die es wagten, SObjectizer zu testen, waren oft davon überzeugt, dass SObjectizer die Entwicklung einiger Arten von Multithread-C ++ - Anwendungen erheblich erleichtert. Natürlich ist SObjectizer kein Wundermittel und kann auf keinen Fall immer verwendet werden. Aber wo es angebracht ist, hilft es.


Das Leben bietet immer wieder die Gelegenheit, sich davon zu überzeugen. Von Zeit zu Zeit fällt uns der Multithread-Code eines anderen auf, in dem es nichts Vergleichbares zu SObjectizer gab und der wahrscheinlich nie auftaucht. Behandeln Sie diesen Code hier und da. Momente, in denen die Verwendung von Akteuren oder CSP-Kanälen den Code sowohl einfacher als auch zuverlässiger machen könnte, sind bemerkenswert. Aber nein, Sie müssen nicht-triviale Muster der Thread-Interaktion mithilfe von mutex-s und condition_variables erstellen, wobei Sie in SObjectizer mit einer mchain, ein paar Nachrichten und einem in SObjectizer integrierten Timer zurechtkommen können. Und dann auch noch viel Zeit damit verbringen, diese nicht trivialen Schemata zu testen ...


SObjectizer war also nützlich für uns. Ich wage zu glauben, dass es nicht nur für uns nützlich war. Und vor allem ist es schon lange hier und steht jedem frei zur Verfügung. Er wird nirgendwo abreisen. Und wohin soll man gehen, was in OpenSource unter einer genehmigten Lizenz steht? ;)


Eine andere Sache ist, dass wir selbst alle unsere großen Wishlist in SObjectizer implementiert haben. Und die zukünftige Entwicklung von SObjectizer wird weniger von unseren Bedürfnissen als vielmehr von den Wünschen der Anwender bestimmt.


Es wird solche Wünsche geben - es wird neue Funktionen in SObjectizer geben.


Es wird nicht so sein ... Nun, dann werden wir von Zeit zu Zeit nur Korrekturversionen herausgeben und die Leistung von SObjectizer unter den neuen Versionen von C ++ - Compilern überprüfen.


Wenn Sie also etwas in SObjectizer sehen möchten, lassen Sie es uns wissen. Wenn Sie Hilfe zu SObjectizer benötigen, können Sie sich gerne an uns wenden (über Probleme mit GitHub oder der Google-Gruppe ). Wir werden uns auf jeden Fall bemühen, Ihnen zu helfen.


Nun, ich möchte mich bei den Lesern bedanken, die bis zum Ende dieses Artikels lesen konnten. Und ich werde versuchen, alle Fragen zu SObjectizer / so5extra zu beantworten, falls solche auftauchen.


PS. Ich wäre dankbar, wenn die Leser Zeit hätten, in den Kommentaren zu schreiben, ob es interessant / nützlich ist, Artikel über SObjectizer zu lesen, und ob sie dies in Zukunft tun möchten. Oder ist es für uns besser, keine Zeit mehr mit dem Schreiben solcher Artikel zu verschwenden und uns nicht mehr die Zeit der Habr-Benutzer zu nehmen?


PPS Oder könnte jemand, der SObjectizer als Werkzeug betrachtet, aus dem einen oder anderen Grund nicht angewendet werden? Es wäre sehr interessant, darüber zu wissen.

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


All Articles