Wie behandelt man ein Thread-Problem in ZeroMQ + Ruby?

8

Stolpern Sie beim Lesen der ZeroMQ-FAQ über eine Thread-Sicherheit.

  

Mein Multithread-Programm stürzt an merkwürdigen Stellen in der ZeroMQ-Bibliothek ab. Was mache ich falsch?

     

ZeroMQ-Sockets sind nicht threadsicher. Dies wird im Handbuch ausführlich behandelt.

     

Die kurze Version ist, dass Sockets nicht zwischen Threads geteilt werden sollten. Wir empfehlen, für jeden Thread einen eigenen Socket zu erstellen.

     

Für Situationen, in denen ein dedizierter Socket pro Thread nicht möglich ist, kann ein Socket genau dann geteilt werden, wenn jeder Thread vor dem Zugriff auf den Socket eine vollständige Speicherbarriere ausführt. Die meisten Sprachen unterstützen einen Mutex oder Spinlock, der die volle Speicherbarriere in Ihrem Auftrag ausführt.

Mein Multithread-Programm stürzt an merkwürdigen Stellen in der ZeroMQ-Bibliothek ab Was mache ich falsch?

Folgendes ist mein folgender Code:

%Vor%

Nun, es gibt einige Dinge, die mir unklar sind:

1) Vorausgesetzt, dass async eine neue Thread (jedes Mal) erzeugt und die write_socket zwischen allen Threads aufgeteilt ist und ZeroMQ angibt, dass ihr Socket nicht Thread-sicher ist. Ich sehe sicherlich das write_socket läuft in Threads Sicherheitsproblem.
(Btw, hat dieses Problem in allen End-to-End-Tests bisher nicht konfrontiert.)

Frage 1 : Stimmt mein Verständnis dafür?

Um dies zu lösen, fordert ZeroMQ uns auf, dies mit Mutex, Semaphore zu erreichen.

Was zu Frage 2

führt

2) Kontextwechsel.

Gegeben eine Thread-Anwendung kann den Kontext jederzeit wechseln. Betrachtet man den ffi-rzmq-Code Celluloid::ZMQ .send() ruft intern send_strings () , die intern send_multiple()

aufgerufen wird

Frage 2: Kontextwechsel kann (überall) passieren (auch in kritischen Bereichen) (hier) [ Ссылка

Dies kann auch zu einem Datenproblem führen.

Ist meine folgende Beobachtung richtig?

Hinweis:

%Vor%     
Viren 04.08.2016, 17:15
quelle

2 Antworten

7

Niemand sollte die Robustheit der Anwendung riskieren, indem er sie auf dünnes Eis legt

Verzeihen Sie, dass diese Geschichte eine ziemlich lange Lektüre ist, aber die lebenslange Erfahrung der Autoren zeigt, dass Gründe warum viel wichtiger sind als einige wenige SLOCs (möglicherweise zweifelhaft oder mystisch aussehende oder unwissende) Versuche, wie

zu finden

Anfangsnotiz

Während ZeroMQ seit mehreren Jahrzehnten als Zero-Sharing (Zero-Blocking, (fast) -Zero-Latency und ein paar mehr Design-Maximen beworben wird. Der beste Ort, um über Pros & amp; Cons zu lesen, sind Pieter HINTJENS 'Bücher , nicht nur die fabelhafte "Code Connected, Volume 1", sondern auch die fortschrittliche Design und Engineering in realen Social-Domain-Philosophie, die jüngste API-Dokumentation eingeführt und wirbt einige IMHO Features mit entspannter Beziehung zu diesen Eckstein Prinzipien für verteiltes Rechnen, die bei Zero-Sharing nicht so scharf pfeifen. Das heißt, ich bin immer noch ein Zero-Sharing-Typ, so freundlich den Rest dieses Beitrags in diesem Licht zu sehen.

Antwort 1:
Nein, Sir. - oder besser - Ja und Nein, Sir.

ZeroMQ bittet Sie nicht, Mutex / Semaphore-Barrieren zu verwenden. Dies widerspricht den Designmaximen von ZeroMQ.

Ja, bei kürzlichen API-Änderungen wurde erwähnt, dass (unter einigen zusätzlichen Bedingungen) man kann Shared-Sockets ... mit (vielen) zusätzlichen Takten verwenden. Also war die Implikation umgekehrt. Wenn jemand "will", nimmt der eine auch alle zusätzlichen Schritte und Maßnahmen (und zahlt alle anfänglich versteckten Kosten für Design und Implementierung, um "freigegebenes" Spielzeug zu erlauben, den (nicht notwendigen) Kampf mit dem Rest (hoffentlich) zu überleben der unkontrollierbaren verteilten Systemumgebung - und somit plötzlich auch das Risiko des Scheiterns (was aus vielen Gründen nicht der Fall bei der ZeroMQ-Zero-Sharing-Evangelisierung war) - so entscheidet der Benutzer, auf welchem ​​Weg er gehen soll fair.).

Ton & amp; robuste Designs IMHO hatte immer noch besser zu entwickeln als per Initial ZeroMQ API & amp; Evangelisation, wo Zero-Sharing ein Prinzip war.

Antwort 2:
Es gibt By-Design immer eine prinzipielle Ungewissheit über die ZeroMQ-Datenflussordnung, eine der ZeroMQ-Entwurfsmaximen hält Designer davon ab, sich auf nicht unterstützte Annahmen über die Nachrichtenreihenfolge zu verlassen viele andere (Ausnahmen gelten). Es gibt nur eine Gewissheit, dass jede Nachricht, die in die ZeroMQ-Infrastruktur gesendet wird, entweder als vollständige Nachricht oder gar nicht übermittelt wird. So kann man sich nur darauf verlassen, dass bei der Auslieferung keine fragmentierten Wracks auftauchen. Für weitere Details, lesen Sie weiter unten.

ThreadId beweist nichts (außer inproc transport-class verwendet)

Angesichts des internen Entwurfs von ZeroMQ-Daten-Pump-Engines entscheidet die Instantiierung eines
zmq.Context( number_of_IO_threads ) darüber, wie viele Threads für die Verarbeitung der zukünftigen Datenflüsse generiert werden. Dies könnte irgendwo sein (0, 1: Standard, 2, ..), bis die Kernel-fixierte maximale Anzahl an Threads fast aufgebraucht ist. Der Wert 0 ergibt eine vernünftige Wahl, keine Ressourcen zu verschwenden, wenn inproc:// transport-class tatsächlich eine direktspeicher-gemappte Handhabung des Datenflusses ist (die eigentlich niemals ang ang fließt, wird direkt in den Landeplatz genagelt) der empfangenden Socket-Abstraktion: o)) und kein Thread wird jemals für einen solchen Job benötigt.
Darüber hinaus ermöglicht das <aSocket>.setsockopt( zmq.AFFINITY, <anIoThreadEnumID#> ) eine Feinabstimmung der datenbezogenen IO- "Hydraulik", um die Thread-Lasten zu priorisieren, Lasten zu balancieren und die Leistung auf den aufgezählten Pool zu optimieren von zmq.Context() -instances IO-Threads und Gain von besseren und besten Einstellungen im oben aufgeführten Design & amp; Datenfluss-Aspekte.

Das Eckstein-Element ist die Context() s-Instanz, und nicht ein Socket() eins

Sobald eine Instanz von Context() instanziiert und konfiguriert wurde (siehe oben, warum und wie), ist sie (fast) frei zu teilen (wenn das Design dem Teilen nicht widerstehen kann oder ein Muss vermeiden muss) Aufbau einer vollwertigen verteilten Rechnerinfrastruktur).

Mit anderen Worten, das Gehirn befindet sich immer in der Instanz von zmq.Context() - alle Socket-bezogenen dFSA-Engines werden dort eingerichtet / konfiguriert / betrieben (ja, obwohl die Syntax ist <aSocket>.setsockopt(...) Der Effekt von solchem ​​ist innerhalb des Gehirns - in der jeweiligen zmq.Context - nicht in einigen Wire-from-A-to-B implementiert.

Besser nie <aSocket> teilen (auch wenn API-4.2.2+ verspricht, du könntest)

Bisher hat man vielleicht eine Menge Code-Snippets gesehen, in denen ZeroMQ Context und seine Sockets sofort instanziiert und entsorgt werden und nur ein paar SLOC-s in einer Reihe haben, aber - das heißt nicht , dass diese Praxis weise ist oder durch andere Bedürfnisse angepasst wird als durch ein sehr akademisches Beispiel (das nur aufgrund der Richtlinien des Buchherausgebers in so wenigen SLOCs gedruckt werden musste wie möglich).

Selbst in solchen Fällen sollte eine faire Warnung vor wirklich immensen Kosten von% Infrastrukturaufbau / -abbau vorhanden sein, um also jegliche Verallgemeinerung zu vermeiden, desto weniger Kopien / Paste-Replikate des Codes, die kurz verwendet wurden nur für solche illustrativen Zwecke.

Stellen Sie sich die realistischen Setups vor, die für jede einzelne zmq.Context -Instanz benötigt werden - um einen Pool von entsprechenden dFSA-Engines zu erstellen, die alle ihre jeweiligen Konfigurations-Setups und alle Socket-End-Point-Pools enthalten. Klassenspezifische Hardware + externe O / S-Services-Handler, Round-Robin-Event-Scanner, Buffer-Memory-Pools-Allokationen + deren Dynamic-Allocators etc, etc. Das alles benötigt sowohl Zeit als auch O / S-Ressourcen ) Kosten weise und mit Sorgfalt für angepasste Gemeinkosten, wenn die Leistung nicht leiden soll.

Wenn Sie sich noch nicht sicher sind, warum Sie dies erwähnen sollten, stellen Sie sich vor, jemand würde darauf bestehen, alle LAN-Kabel sofort nach dem Senden eines Pakets abzureißen und warten zu müssen, bis eine neue Verkabelung installiert ist Das nächste Paket wird angezeigt. Ich hoffe, diese "vernünftige Instanziierung" -Ansicht könnte nun besser wahrgenommen werden und ein Argument, um (wenn überhaupt) eine Context -Instanz (en) zu teilen, ohne weitere Kämpfe für den Versuch, ZeroMQ-Socket-Instanzen zu teilen (selbst wenn es neu wird) (fast) threadsicher per se).

Die ZeroMQ-Philosophie ist robust, wenn sie als fortschrittliche Design-Evangelisation für hochleistungsfähige verteilte Computerinfrastrukturen betrachtet wird. Das Anpassen nur eines (kleinen) Aspekts passt normalerweise nicht alle Bemühungen und Kosten an, wie auf der globalen Ansicht, wie sichere und performante Systeme entworfen werden, das Ergebnis würde sich nicht ein bisschen besser bewegen (Und selbst die absolut freigebbaren risikofreien (wenn überhaupt möglich) Socket-Instanzen werden dies nicht ändern, während alle Vorteile für Sound-Design, Clean-Code und vernünftig erreichbare Testfähigkeit und Debugging erhalten werden verloren) wenn nur dieses eine Detail geändert wird - also lieber einen anderen Draht aus einem bestehenden Gehirn zu einem solchen neuen Thread ziehen, oder einen neuen Thread mit seinem eigenen Gehirn ausstatten, der seine Resourcen lokal behandeln und es erlauben wird, eigene Drähte zu verbinden zurück zu allen anderen Gehirnen - wie notwendig, um mit zu kommunizieren - im verteilten System.

Wenn Sie noch Zweifel haben, versuchen Sie sich vorzustellen, was mit Ihrer nationalen Eishockey-Nationalmannschaft passieren würde, wenn sie während des Turniers nur einen einzigen Hockeyschläger teilen würde. Oder wie möchten Sie, wenn alle Nachbarn in Ihrer Stadt die gleiche Telefonnummer teilen würden, um alle eingehenden Anrufe zu beantworten (ja, mit allen Telefonen und Handys, die die gleiche Nummer teilen, gleichzeitig). Wie gut würde das funktionieren?

Sprachbindungen müssen nicht alle verfügbaren API-Funktionen enthalten

Hier kann man ansprechen, und in einigen Fällen ist es richtig, dass nicht alle ZeroMQ-Sprachbindungen oder alle gängigen Framework-Wrapper alle API-Details dem Benutzer für die Programmierung auf Anwendungsebene zugänglich machen (der Autor dieses Beitrags hatte Probleme damit) Eine lange Zeit mit solchen Legendenkonflikten, die aus diesem Grund unlösbar blieben und sich den Kopf kratzen mussten, um einen gangbaren Weg zu finden, um diese Tatsache zu umgehen - so ist es (fast) immer machbar)

Epilog:

Es ist anzumerken, dass neuere Versionen von ZeroMQ API 4.2.2+ anfänglich die evangelisierenden Prinzipien durchschimmerten.

Trotzdem lohnt es sich, an das unruhige memento mori

zu denken

(Hervorhebungen hinzugefügt, Großschreibung nicht)

  

Fadensicherheit

     

ØMQ hat beide thread-sicheren Socket-Typen und keine thread-sicheren Socket-Typen. Anwendungen DÜRFEN NICHT einen nicht threadsicheren Socket aus mehreren Threads verwenden, außer nach der Migration eines Sockets von einem Thread zu einem anderen mit einer "Full Fence" Speicherbarriere.

     

Es folgen die threadsicheren Sockets: * zmq.Context() * ZMQ_CLIENT * ZMQ_SERVER * ZMQ_DISH * ZMQ_RADIO * ZMQ_SCATTER

Während dieser Text für einige Ohren als vielversprechend empfunden wird, ist das Schlimmste, das man bei der Entwicklung fortschrittlicher verteilter Rechensysteme anstellen kann, wo Leistung ein Muss ist.

Das letzte, was man gerne sehen möchte, ist, seinen eigenen Code zu blockieren, da dieser Agent in einen prinzipiell unkontrollierbaren Blockierungszustand gerät, in dem niemand es absetzen kann (weder der Agent selbst, noch jemand aus außerhalb), falls ein entfernter Agent niemals ein - gerade - erwartetes Ereignis liefert (was in verteilten Systemen durch so viele Gründe oder unter so vielen Umständen geschehen kann, die außerhalb der Kontrolle liegen).

Wenn man ein System baut, das dazu neigt, sich selbst aufzuhängen (mit einem breiten Lächeln der unterstützten (aber naiv eingesetzten) Syntax-Möglichkeit), ist das in der Tat nichts, was ein seriöser Design-Job ist.

Man würde sich auch nicht wundern, dass viele zusätzliche (anfänglich nicht sichtbare) Einschränkungen die Grenzen der neuen Schritte in der Verwendung von shared-hockey-stick | Telefone} API:

  

ZMQ_GATHER sockets sind threadsafe. Sie akzeptieren die ZMQ_CLIENT -Option nicht, wenn nicht ZMQ_SNDMORE on empfangen wird. Dies beschränkt sie auf einzelne Teildaten . Die Absicht ist, die API zu erweitern, um das Scatter / Gather von mehrteiligen Daten zu erlauben.

c / a

ZMQ_RCVMORE meldet keine dieser neuen API-Socket-Typen (eine Sünde des Teilens fast fehlerverzeihender) in seinem Abschnitt über unterstützte Sockets, so dass keine guten Nachrichten zu erwarten sind, die a-priori und Celluloid::ZMQ Master-Aktivität zu erwarten sind irgendwo im Jahr 2015 ausgeblendet zu sein, also sollten die Erwartungen aus dieser Ecke realistisch sein.

Dies gesagt, könnte ein interessanter Punkt hinter einer Nachricht gefunden werden:

  

bevor Sie bauen Ihre eigenen verteilten Celluloid-Systeme mit Celluloid::ZMQ , achten Sie darauf, DCell einen Blick und entscheiden, ob es passt zu deinen Absichten.

Last but not least ist das Kombinieren des Ereignis-Schleifen-Systems innerhalb einer anderen Ereignis-Schleife ein schmerzhafter Job. Der Versuch, ein eingebettetes Hard-Real-Time-System in ein anderes Hard-Real-Time-System zu integrieren, könnte sich sogar mathematisch als unmöglich erweisen.

In ähnlicher Weise bringt der Aufbau eines Multi-Agenten-Systems mit einer anderen agentenbasierten Komponente zusätzliche Arten von Kollisionen und Race-Conditions, wenn dieselben Ressourcen genutzt werden (sei es wissentlich oder durch "nur" einige funktionale Nebeneffekte). von beiden (multiplen) agentenbasierten Frameworks.

Nicht-rettbare gegenseitige Dead-Locks sind nur eine Art dieser Kollisionen, die anfänglich noch nicht gesehene Probleme in Richtung unbewusster Entwurfsversuche führen. Der allererste Schritt außerhalb eines Single-Agent-Systemdesigns führt dazu, dass man viele weitere Garantien verliert, die vor der Teilnahme an Multi-Agenten (verteilt) nicht bemerkt wurden, also offen sind und bereit sind, viele "neue" Konzepte und Konzentrationen zu lernen auf viele neue Anliegen, die sorgfältig beobachtet und bekämpft werden müssen, sind eine wichtige Voraussetzung, um nicht (unbewusst) Muster einzuführen, die jetzt tatsächlich Anti-Muster in verteilten (Multi-Agenten-) Domänen sind / p>

Mindestens
Sie wurden gewarnt:: o)

    
user3666197 06.08.2017, 16:16
quelle
0

Diese Antwort ist keine gute Lösung für Ihr Problem, und Sie gehen definitiv mit dem, was Benutzer3666197 vorschlägt. Ich denke, dass diese Lösung das Potenzial hat, zu arbeiten, aber auch in großem Maßstab kann es aufgrund von Mutex-Überlastung zu Leistungseinbußen kommen.

  

Frage 1: Vorausgesetzt, dass async einen neuen Thread erzeugt (jedes Mal) und write_socket zwischen allen Threads geteilt wird und zeromq sagt, dass ihr Socket nicht sicher ist. Ich sehe sicherlich, dass write_socket in Threads Sicherheitsfrage läuft. (Btw hat dieses Problem bisher bei allen End-to-End-Tests nicht gesehen.) Stimmt mein Verständnis dafür?

Nach meinem Verständnis der Dokumentation könnte dies ein Problem darstellen, da die Sockets nicht Thread-sicher sind. Auch wenn das Problem nicht auftritt, könnte es später auftauchen.

  

Frage 2: Kontextwechsel kann (überall) passieren (auch im kritischen Bereich)

Ja, eine Möglichkeit, wie wir das umgehen können, ist ein Mutex / Semaphor, um sicherzustellen, dass wir zum falschen Zeitpunkt keinen Kontextwechsel haben.

Ich würde so etwas tun, aber es könnte einen etwas besseren Ansatz geben, je nachdem, welche aufgerufenen Methoden nicht threadsicher sind:

%Vor%     
Dbz 06.08.2017 13:49
quelle