
Am dritten Tag wurde eine neue Version von SObjectizer verfügbar : 5.6.0 . Das Hauptmerkmal ist die Ablehnung der Kompatibilität mit dem vorherigen stabilen Zweig 5.5, der sich im Laufe von viereinhalb Jahren stetig weiterentwickelt hat.
Die Grundprinzipien des Betriebs von SObjectizer-5 blieben unverändert. Kommunikation, Agenten, Kooperationen und Disponenten sind immer noch bei uns. Aber etwas ernsthaft geändert, etwas wurde allgemein weggeworfen. Daher schlägt es fehl, nur SO-5.6.0 zu verwenden und Ihren Code neu zu kompilieren. Etwas muss neu geschrieben werden. Möglicherweise muss etwas neu gestaltet werden.
Warum haben wir uns mehrere Jahre lang um die Kompatibilität gekümmert und dann beschlossen, alles zu nehmen und zu brechen? Und was ist am gründlichsten kaputt gegangen?
Ich werde versuchen, in diesem Artikel darüber zu sprechen.
Warum musstest du etwas kaputt machen?
So einfach ist das.
SObjectizer-5.5 hat während seiner Entwicklung so viel von jedem anderen und vielfältigen aufgenommen, was ursprünglich nicht geplant war, dass es zu viele Krücken und Requisiten im Inneren gebildet hat. Mit jeder neuen Version wurde es immer schwieriger, SO-5.5 etwas Neues hinzuzufügen. Und schließlich zur Frage "Warum brauchen wir das alles?" Es wurde keine passende Antwort gefunden.
Der erste Grund ist also die erneute Komplikation der Innereien von SObjectizer.
Der zweite Grund ist, dass wir es dumm sind, uns auf alte C ++ - Compiler zu konzentrieren. Zweig 5.5 begann 2014, als wir, wenn ich mich nicht irre, gcc-4.8 und MSVS2013 hatten. Auf dieser Ebene haben wir die Anforderungen an die Unterstützung des C ++ - Standards weiterhin beibehalten.
Anfangs hatten wir „egoistisches Interesse“ daran. Darüber hinaus betrachteten wir einige Zeit die geringen Anforderungen an die Qualität der Unterstützung für den C ++ - Standard als unseren "Wettbewerbsvorteil".
Aber die Zeit vergeht, "egoistisches Interesse" ist vorbei. Einige Vorteile eines solchen "Wettbewerbsvorteils" sind nicht sichtbar. Vielleicht wären sie, wenn wir überhaupt mit C ++ 98 arbeiten würden, an blutigen Unternehmungen interessiert. Aber das blutige Unternehmen in solchen wie uns ist im Prinzip nicht interessiert. Daher wurde beschlossen, uns nicht mehr einzuschränken und etwas Frischeres zu nehmen. Also haben wir im Moment den frischesten aus dem Stall genommen: C ++ 17.
Offensichtlich wird diese Lösung nicht jedem gefallen, denn für viele C ++ 17 ist dies jetzt eine unerreichbare Vorderkante, die immer noch sehr, sehr weit entfernt ist.
Trotzdem haben wir uns für ein solches Risiko entschieden. Trotzdem geht der Prozess der Popularisierung von SObjectizer nicht schnell voran. Wenn SObjectizer mehr oder weniger gefragt ist, wird C ++ 17 keine "führende Kante" mehr sein. Vielmehr wird es genauso behandelt wie jetzt in C ++ 11.
Anstatt weiterhin Krücken mit einer Teilmenge von C ++ 11 zu bauen, haben wir uns im Allgemeinen entschlossen, die Interna von SObjectizer mit C ++ 17 ernsthaft neu zu gestalten. Aufbau einer Basis, auf der sich SObjectizer in den nächsten vier oder fünf Jahren schrittweise entwickeln kann.
Was hat sich in SObjectizer-5.6 ernsthaft geändert?
Lassen Sie uns nun einige der auffälligsten Änderungen kurz betrachten.
Agentenkooperationen haben keine Zeichenfolgennamen mehr
Das Problem
SObjectizer-5 forderte von Anfang an, dass jede Kooperation einen eigenen eindeutigen Stringnamen hat. Diese Funktion wurde vom fünften SObjectizer vom vorherigen vierten SObjectizer übernommen.
Dementsprechend musste SObjectizer die Namen registrierter Kooperationen speichern. Überprüfen Sie ihre Eindeutigkeit bei der Registrierung. Suche nach namentlicher Zusammenarbeit bei der Abmeldung usw. usw.
Seit den ersten Versionen wurde in SObjectzer-5 ein einfaches Schema verwendet: ein einziges Wörterbuch registrierter Kooperationen, die durch Mutex geschützt sind. Bei der Registrierung einer Kooperation wird der Mutex erfasst, die Eindeutigkeit des Namens der Kooperation, die Anwesenheit eines Elternteils usw. Nach der Überprüfung wird das Wörterbuch geändert, woraufhin der Mutex freigegeben wird. Dies bedeutet, dass, wenn gleichzeitig die Registrierung / Abmeldung mehrerer Kooperationen gleichzeitig beginnt, sie an einigen Stellen pausieren und warten, bis eine der Operationen die Arbeit mit dem kooperativen Wörterbuch abgeschlossen hat. Aus diesem Grund waren die kooperativen Operationen nicht gut skalierbar.
Das wollte ich loswerden, um die Situation mit der Geschwindigkeit der Registrierung von Kooperationen zu verbessern.
Lösung
Es wurden zwei Hauptwege zur Lösung dieses Problems in Betracht gezogen.
Erstens das Speichern von Zeichenfolgennamen, aber das Ändern der Art und Weise, wie das Wörterbuch gespeichert wird, damit die Kooperationsregistrierungsoperation skaliert werden kann. Zum Beispiel Wörterbuch-Sharding, d.h. Brechen Sie es in mehrere Teile, von denen jedes durch seinen Mutex geschützt wäre.
Zweitens eine vollständige Ablehnung von Zeichenfolgennamen und die Verwendung einiger von SObjectizer zugewiesener Bezeichner.
Infolgedessen haben wir uns für die zweite Methode entschieden und die Benennung von Genossenschaften vollständig aufgegeben. Jetzt gibt es in SObjectizer so etwas wie coop_handle
, d.h. Ein Handle, dessen Inhalt dem Benutzer verborgen std::weak_ptr
, das jedoch mit std::weak_ptr
verglichen werden std::weak_ptr
.
SObjectizer gibt coop_handle
wenn eine Zusammenarbeit registriert wird:
auto coop = env.make_coop(); ...
Dieser Handle sollte für die Abmeldung der Zusammenarbeit verwendet werden:
auto coop = env.make_coop(); ...
Dieses Handle sollte auch beim Aufbau einer Eltern-Kind-Beziehung verwendet werden:
Die Struktur des Repositorys für die Zusammenarbeit innerhalb der SObjectizer-Umgebung hat sich ebenfalls dramatisch geändert. Wenn es vor Version 5.5 einschließlich ein gemeinsames Wörterbuch war, ist jetzt jede Kooperation ein Repository mit Links zu untergeordneten Kooperationen. Das heißt, Genossenschaften bilden einen Baum mit einer Wurzel in einer speziellen Wurzelgenossenschaft, die dem Benutzer verborgen bleibt.
Eine solche Struktur ermöglicht es, die deregister_coop
register_coop
und deregister_coop
viel besser zu skalieren: Die gegenseitige Blockierung paralleler Operationen erfolgt nur, wenn beide zur gleichen übergeordneten Zusammenarbeit gehören. Der Klarheit halber ist hier das Ergebnis der Einführung eines speziellen Benchmarks , der die Leistung von Operationen mit Kooperationen auf meinem alten Laptop mit Ubuntu 16.04 und GCC-7.3 misst:
_test.bench.so_5.parallel_parent_child -r 4 -l 7 -s 5 Configuration: roots: 4, levels: 7, level-size: 5 parallel_parent_child: 15.69s 488280 488280 488280 488280 Total: 1953120
Das heißt, Version 5.6.0 bewältigte fast 2 Millionen Kooperationen in ~ 15,5 Sekunden.
Und hier ist Version 5.5.24.4, die letzte von Zweig 5.5 im Moment:
_test.bench.so_5.parallel_parent_child -r 4 -l 7 -s 5 Configuration: roots: 4, levels: 7, level-size: 5 parallel_parent_child: 46.856s 488280 488280 488280 488280 Total: 1953120
Das gleiche Szenario, aber das Ergebnis ist dreimal schlechter.
Es gibt nur noch eine Art von Disponenten
Dispatcher sind einer der Eckpfeiler von SObjectizer. Es sind die Disponenten, die bestimmen, wo und wie Agenten ihre Nachrichten verarbeiten. Ohne die Idee der Disponenten hätte es wahrscheinlich keinen SObjectizer gegeben.
Die Disponenten selbst haben sich jedoch so weit entwickelt, dass es für uns nicht einmal schwierig war, einen neuen Dispatcher für SObjectizer-5.5 zu entwickeln. Aber sehr mühsam. Nehmen wir es jedoch in Ordnung.
Zunächst konnten alle Dispatcher, die die Anwendung benötigte, erst zu Beginn des SObjectizer erstellt werden:
so_5::launch( []( so_5::environment_t & env ) { },
Ich habe vor dem Start nicht den erforderlichen Dispatcher erstellt - alles, es ist meine Schuld, Sie können nichts ändern.
Es ist klar, dass dies unpraktisch ist, und als die Verwendungsszenarien für SObjectizer erweitert wurden, musste dieses Problem gelöst werden. Daher wurde die Methode add_dispatcher_if_not_exists
, die das Vorhandensein eines Dispatchers prüfte und, falls keine vorhanden war, eine neue Instanz erstellen durfte:
so_5::launch( []( so_5::environment_t & env ) { ...
Solche Disponenten wurden als öffentlich bezeichnet. Öffentliche Disponenten hatten eindeutige Namen. Und unter diesen Namen waren die Agenten an die Disponenten gebunden:
so_5::launch( []( so_5::environment_t & env ) { ...
Aber öffentliche Disponenten hatten eine unangenehme Eigenschaft. Sie begannen sofort nach dem Hinzufügen zur SObjectizer-Umgebung mit der Arbeit und arbeiteten weiter, bis die SObjectizer-Umgebung ihre Arbeit abgeschlossen hatte.
Im Laufe der Zeit begann es wieder zu stören. Es musste sichergestellt werden, dass Disponenten nach Bedarf hinzugefügt werden konnten und dass unnötige Disponenten automatisch gelöscht wurden.
Es gab also "private" Disponenten. Diese Disponenten hatten keine Namen und lebten, solange es Hinweise auf sie gab. Private Dispatcher konnten jederzeit nach dem Start der SObjectizer-Umgebung erstellt werden, sie wurden automatisch zerstört.
Im Allgemeinen erwiesen sich private Disponenten als sehr erfolgreiches Bindeglied in der Entwicklung der Disponenten, aber die Zusammenarbeit mit ihnen unterschied sich stark von der Arbeit mit öffentlichen Dispatchern:
so_5::launch( []( so_5::environment_t & env ) { ...
Noch mehr private und öffentliche Disponenten unterschieden sich in der Umsetzung. Um den Code nicht zu duplizieren und den öffentlichen und den privaten Dispatcher des gleichen Typs nicht getrennt zu schreiben, musste ich daher ziemlich komplexe Konstruktionen mit Vorlagen und Vererbung verwenden.
Infolgedessen war diese ganze Sorte der Begleitung überdrüssig, und in SObjectizer-5.6 gab es nur noch eine Art von Dispatchern. In der Tat ist dies ein Analogon von privaten Dispatchern. Aber nur ohne ausdrückliche Erwähnung des Wortes "privat". Das oben gezeigte Fragment wird nun wie folgt geschrieben:
so_5::launch( []( so_5::environment_t & env ) { ...
Es sind nur noch freie Funktionen send, send_delayed und send_periodic übrig
Die Entwicklung der API zum Senden von Nachrichten an SObjectizer ist wahrscheinlich das auffälligste Beispiel dafür, wie sich SObjectizer geändert hat, da sich die Unterstützung für C ++ 11 in den uns zur Verfügung stehenden Compilern verbessert hat.
Zuerst wurden Nachrichten wie folgt gesendet:
mbox->deliver_message(new my_message(...));
Oder wenn Sie den "Empfehlungen der besten Hundezüchter" (c) folgen:
std::unique_ptr<my_message> msg(new my_message(...)); mbox->deliver_message(std::move(msg));
Dann standen uns jedoch Compiler mit Unterstützung für verschiedene Vorlagen zur Verfügung, und es erschienen Sendefunktionen. Es wurde möglich, so zu schreiben:
send<my_message>(target, ...);
Es ist wahr, dass es eine ganze send_to_agent
send_delayed_to_agent
, bis eine ganze Familie aus dem einfachen send
, einschließlich send_to_agent
, send_delayed_to_agent
usw. Und um diese Familie auf den bekannten Satz von send
, send_delayed
und send_periodic
.
Trotz der Tatsache, dass die Familie der deliver_message
langer Zeit gegründet wurde und seit mehreren Jahren die empfohlene Methode zum Senden von Nachrichten ist, standen dem Benutzer immer noch alte Methoden wie deliver_message
, schedule_timer
und single_timer
zur Verfügung.
In Version 5.6.0 wurden jedoch nur die Funktionen free send
, send_delayed
und send_periodic
in der öffentlichen SObjectizer-API gespeichert. Alles andere wurde entweder vollständig gelöscht oder in interne SObjectizer-Namespaces übertragen.
In SObjectizer-5.6 ist die Schnittstelle zum Senden von Nachrichten endlich so geworden, wie es gewesen wäre, wenn wir von Anfang an Compiler mit normaler C ++ 11-Unterstützung gehabt hätten. Nun, außerdem, wenn wir Erfahrung mit diesem ganz normalen C ++ 11 hatten.
Bei den send_periodic
send_delayed
und send_periodic
in früheren Versionen von SObjectizer gab es einen weiteren Vorfall.
Um den Timer verwenden zu können, müssen Sie Zugriff auf die SObjectizer-Umgebung haben. Innerhalb des Agenten gibt es einen Link zur SObjectizer-Umgebung. Und innerhalb von mchain gibt es einen solchen Link. Aber in der Mbox war sie nicht da. Wenn daher eine ausstehende Nachricht an einen Agenten oder an mchain send_delayed
, send_delayed
der Aufruf send_delayed
:
send_delayed<my_message>(target_agent, pause, ...); send_delayed<my_message>(target_mchain, pause, ...);
Für den Fall von mbox mussten wir von irgendwo anders einen Link zur SObjectizer-Umgebung nehmen:
send_delayed<my_message>(this->so_environment(), target_mbox, pause, ...);
Diese Funktion von send_delayed
und send_periodic
war ein kleiner Splitter. Welches ist nicht so viel stören, aber nervig hübsch. Und das alles, weil wir anfangs nicht damit begonnen haben, den Link zur SObjectizer-Umgebung in mbox-ahs zu speichern.
Die Verletzung der Kompatibilität mit früheren Versionen war ein guter Grund, diesen Splitter loszuwerden.
Jetzt können Sie in mbox herausfinden, für welche SObjectizer-Umgebung sie erstellt wurde. Dies ermöglichte es, die einzelnen send_periodic
send_delayed
und send_periodic
für jede Art von Timer-Nachrichtenempfänger zu verwenden:
send_delayed<my_message>(target_agent, pause, ...); send_delayed<my_message>(target_mchain, pause, ...); send_delayed<my_message>(target_mbox, pause, ...);
Im wahrsten Sinne des Wortes "eine Kleinigkeit, aber nett".
Keine Ad-hoc-Agenten mehr
Wie das Sprichwort sagt: "Jeder Unfall hat einen Vornamen, einen zweiten Vornamen und einen Nachnamen." Bei Ad-hoc-Agenten ist dies mein Vor- und Nachname sowie mein Nachname :(
Der Punkt ist dies. Als wir in der Öffentlichkeit über SObjectizer-5 sprachen, hörten wir viele Vorwürfe über die Ausführlichkeit des Codes für die Beispiele von SObjectizer. Und persönlich schien mir diese Ausführlichkeit ein ernstes Problem zu sein, mit dem ich mich ernsthaft befassen muss.
Eine Quelle der Ausführlichkeit ist die Notwendigkeit, dass Agenten vom speziellen Basistyp agent_t
. Und davon scheint es kein Entrinnen zu geben. Oder nicht?
Es gab also Ad-hoc-Agenten, d.h. Agenten, für deren Bestimmung es nicht notwendig war, eine separate Klasse zu schreiben, genügte es, nur die Reaktion auf Nachrichten in Form von Lambda-Funktionen einzustellen. Das klassische Ping-Pong-Beispiel für Ad-hoc-Agenten könnte beispielsweise folgendermaßen geschrieben werden:
auto pinger = coop->define_agent(); auto ponger = coop->define_agent(); pinger .on_start( [ponger]{ so_5::send< msg_ping >( ponger ); } ) .event< msg_pong >( pinger, [ponger]{ so_5::send< msg_ping >( ponger ); } ); ponger .event< msg_ping >( ponger, [pinger]{ so_5::send< msg_pong >( pinger ); } );
Das heißt, keine eigenen Klassen. Wir rufen einfach define_agent()
für die Zusammenarbeit auf und erhalten eine Art define_agent()
, mit dem Sie eingehende Nachrichten abonnieren können.
In SObjectizer-5 gab es also eine Trennung in reguläre und Ad-hoc-Agenten.
Was keine sichtbaren Boni brachte, nur die zusätzlichen Arbeitskosten, die mit einer solchen Trennung einhergingen. Und im Laufe der Zeit wurde klar, dass Ad-hoc-Agenten wie ein Koffer ohne Griff sind: Es ist schwer zu tragen und es ist schade, zu gehen. Während der Arbeit an SObjectizer-5.6 wurde jedoch beschlossen, das Programm zu beenden.
Gleichzeitig wurde eine weitere, vielleicht sogar noch wichtigere Lektion gelernt: An jeder öffentlichen Diskussion über das Tool im Internet wird eine große Anzahl von Personen teilnehmen, denen es gleichgültig ist, was das Tool ist, warum es benötigt wird, warum es so ist, wie es verwendet werden soll usw. Es ist einfach wichtig für sie, ihre starke Meinung zu äußern. Darüber hinaus ist es im russischsprachigen Segment des Internets nach wie vor sehr wichtig, den Entwicklern des Tools zu vermitteln, wie verrückt und ungebildet sie sind und wie sehr das Ergebnis ihrer Arbeit nicht benötigt wird.
Daher sollten Sie sehr vorsichtig sein, was Ihnen gesagt wird. Und Sie können nur genau das hören (und dann genau hören), was hier in diesem Sinne gesagt wird: "Ich habe versucht, dies auf Ihrem Instrument zu tun, und mir gefällt nicht, wie viel Code es hier hat." Auch solche Wünsche sollten sehr sorgfältig behandelt werden: "Ich würde Ihre Entwicklung nehmen, wenn es hier und hier einfacher wäre."
Leider war die Fähigkeit zum "Filtern", die "Gratulanten" im Internet vor etwa fünf Jahren sagten, viel geringer als heute. Daher ein solches spezifisches Experiment wie Ad-hoc-Agenten in SObjectizer.
SObjectizer-5.6 unterstützt keine synchronisierte Agenteninteraktion mehr
Das Thema der synchronisierten Interaktion zwischen Agenten ist sehr alt und wund.
Es begann in den Tagen von SObjectizer-4. Und in SObjectizer-5 weiter. Soweit endlich das sogenannte Serviceanfragen . Was anfangs zugegebenermaßen beängstigend war wie der Tod. Aber dann habe ich es geschafft , ihnen einen mehr oder weniger anständigen Blick zu geben .
Dies stellte sich jedoch als der Fall heraus, als der erste Pfannkuchen klumpig herauskam :(
In SObjectizer musste ich die Zustellung und Verarbeitung regulärer Nachrichten auf die eine und die Zustellung und Verarbeitung synchroner Anforderungen auf eine andere Weise implementieren. Es ist besonders traurig, dass diese Funktionen berücksichtigt werden mussten, auch bei der Implementierung Ihrer eigenen mbox-s.
Und nachdem die Funktionalität von Umschlagnachrichten zu SObjectizer hinzugefügt wurde, wurde es notwendig, die Unterschiede zwischen regulären Nachrichten und synchronen Anforderungen noch häufiger und gründlicher zu untersuchen.
Im Allgemeinen gab es bei synchronen Anforderungen während der Wartung / Entwicklung von SObjectizer zu viele Kopfschmerzen. So sehr, dass zunächst der konkrete Wunsch bestand, diese sehr synchronen Anfragen loszuwerden . Und dann wurde dieser Wunsch verwirklicht.
In SObjectizer-5.6 können Agenten also nur über asynchrone Nachrichten wieder interagieren.
Und da manchmal noch so etwas wie synchrone Interaktion benötigt wird, wurde die Unterstützung für diese Art der Interaktion an das begleitende so5extra-Projekt übermittelt :
Das heißt, Die Arbeit mit synchronen Anforderungen unterscheidet sich grundlegend darin, dass der Anforderungshandler keinen Wert wie zuvor von der Handler-Methode zurückgibt. Stattdessen wird die Methode make_reply
.
Die neue Implementierung ist insofern gut, als sowohl die Anforderung als auch die Antwort wie normale asynchrone Nachrichten im SObjectizer gesendet werden. Tatsächlich ist make_reply
eine etwas spezifischere Implementierung von send
.
Und vor allem ermöglichte uns die neue Implementierung, Funktionen zu erhalten, die zuvor nicht erreichbar waren:
- Synchrone Anforderungen (d.
request_reply_t<Request, Reply>
-Objekte) können jetzt gespeichert und / oder an andere Handler weitergeleitet werden. Was macht es möglich, verschiedene Lastausgleichsschemata zu implementieren? - Sie können die Antwort auf die Anfrage in einer regulären mbox des Agenten erhalten, der die Anfrage initiiert. Und der initiierende Agent verarbeitet die Antwort wie jede andere Nachricht auf die übliche Weise.
- Sie können mehrere Anfragen gleichzeitig an verschiedene Empfänger senden und dann die Antworten von ihnen in der Reihenfolge analysieren, in der sie empfangen wurden:
using first_dialog = so_5::extra::sync::request_reply_t<first_request, first_reply>; using second_dialog = so_5::extra::sync::request_reply_t<second_request, second_reply>;
Wir können also sagen, dass bei der synchronen Interaktion in SObjectizer Folgendes passiert ist:
- lange Zeit war er aus ideologischen Gründen weg;
- dann wurde es hinzugefügt und es stellte sich heraus, dass manchmal eine solche Interaktion nützlich ist;
- Die Erfahrung hat jedoch gezeigt, dass die erste Implementierung nicht sehr erfolgreich ist.
- Die alte Implementierung wurde komplett weggeworfen, und im Gegenzug wurde eine neue Implementierung vorgeschlagen.
Sie haben im Allgemeinen an ihren eigenen Fehlern gearbeitet.
Fazit
In diesem Artikel wurden ganz kurz einige Änderungen in SObjectizer-5.6.0 und die Gründe für diese Änderungen erläutert.
Eine vollständigere Liste der Änderungen finden Sie hier .
Abschließend möchte ich denjenigen, die SObjectizer noch nicht ausprobiert haben, anbieten, es zu nehmen und auszuprobieren. Und teilen Sie uns Ihre Gefühle mit: Was Ihnen gefallen hat, was Ihnen nicht gefallen hat, was gefehlt hat.
Wir hören uns alle konstruktiven Kommentare / Vorschläge genau an. Darüber hinaus ist in SObjectizer in den letzten Jahren nur das enthalten, was jemand benötigt. Wenn Sie uns also nicht mitteilen, was Sie in SObjectizer haben möchten, wird dies nicht angezeigt. Und wenn du es mir sagst, wer weiß dann ...;)
Das Projekt lebt und entwickelt sich jetzt hier . Und für diejenigen, die es gewohnt sind, nur GitHub zu verwenden, gibt es einen GitHub-Spiegel . Dieser Spiegel ist völlig neu, so dass Sie den Mangel an Sternen ignorieren können.
PS. Sie können SObjectizer-bezogene Nachrichten in dieser Google-Gruppe verfolgen. Dort können Sie Probleme im Zusammenhang mit SObjectizer ansprechen.