Pure-Ruby gleichzeitige Hash
Was ist der beste Weg zur Implementierung eines Hash geändert werden kann über mehrere threads, aber mit der kleinsten Anzahl von sperren. Für die Zwecke dieser Frage, können Sie davon ausgehen, dass der Hash gelesen werden-schwer. Es muss thread-safe in allen Ruby-Implementierungen, einschließlich diejenigen, die arbeiten in einer wahrhaft gleichzeitigen Mode, wie JRuby, und es muss geschrieben werden, in pure-Ruby (kein C, Java oder erlaubt).
Fühlen Sie sich frei, um senden Sie eine naive Lösung, die immer sperrt, aber das ist nicht wahrscheinlich, die beste Lösung zu sein. Punkte für Eleganz, aber eine kleinere Wahrscheinlichkeit von sperren Siege über einen kleineren code.
- Ruby 1.8 oder 1.9?
- Ich nehme die Antwort als Werke, die auf Ruby 1.9, solange es funktioniert auf JRuby in 1.9-Modus. Ich bevorzuge eine Antwort als " funktioniert sowohl auf 1.8 und 1.9.
- Wie ist das nützlich? Sicher, Sie haben mittlerweile den Wert sicher, aber es ist nicht abzusehen, wie lange der Wert gültig ist. Könnten Sie ziehen es aus dem hash, der andere könnte geplant werden, und schreiben Sie die gleiche Taste, die alle, bevor Sie etwas tun interessant, mit.
- Nur damit die Leute nicht denken, dass ich aufgegeben habe, werde ich akzeptieren, eine Antwort morgen 🙂
Du musst angemeldet sein, um einen Kommentar abzugeben.
Okay, jetzt, dass Sie angegeben haben, die tatsächlich Bedeutung von "threadsicher", hier werden zwei mögliche Implementierungen. Der folgende code wird ausgeführt für immer im MRT und JRuby. Die lockless Implementierung folgt eine eventual consistency Modell, bei dem jeder thread verwendet es die eigene Ansicht des hash-wenn der Meister im Wandel. Es ist etwas Trickserei erforderlich, um sicher zu speichern alle Informationen, die in dem thread nicht ein Speicherleck, aber das ist behandelt und getestet ― Prozess die Größe nicht wachsen, läuft dieser code. Beide Implementierungen müssten mehr arbeiten, um sein "complete", was bedeutet, delete, update, etc. müssten einige denken, aber eines der beiden Konzepte unter Ihren Anforderungen gerecht wird.
Es ist sehr wichtig für Menschen, die Lesen dieses Threads klar, das ganze Thema ist exklusiv für JRuby ― im MRT der built-in Hash ist ausreichend.
Posting base/naive Lösung, nur um boost meine Stack-Überlauf cred:
Yehuda, ich denke, Sie erwähnten ivar Einstellung war atomic? Was ist mit einem einfachen kopieren-und tauschen dann?
clone
auch kopieren singleton-Methoden in der Erwägung, dassdup
nicht.h[key] = value
würde sprengen.Dies ist eine wrapper-Klasse um die Hash-ermöglicht gleichzeitigen Leser, aber sperren sich die Dinge nach unten für alle anderen Arten von Zugang (einschließlich der iterierten liest).
Hier ist die locking-code verwendet:
Den thread-aware-lock ist, Ihnen zu erlauben, sperren Sie die Klasse einmal, und dann Methoden aufzurufen, die normalerweise sperren und nicht sperren. Sie benötigen diese, da Sie die Ausbeute in Blöcke innerhalb einiger Methoden, und diese Blöcke können Anruf sperren Methoden auf das Objekt, und Sie nicht möchten, dass ein deadlock oder eine double-lock-Fehler. Sie könnten eine Zählung sperren, anstatt für diesen.
Hier ist ein Versuch, zu implementieren bucket-Ebene lese-schreib-sperren:
Habe ich aufgehört zu arbeiten, weil es zu langsam ist, so das jede Methode ist unsicher (können Mutationen durch andere threads während einer iteration) und es unterstützt nicht die meisten hash-Verfahren.
Und hier ist eine Testumgebung für die gleichzeitige hashes:
Zahlen:
Jruby
- Und MRT -
MRT zahlen sind ziemlich Auffällig. Verriegelung in der MRT ist wirklich beschissen.
Könnte dies ein Fall für das hamster gem
Hamster implementiert Hash-Array Abgebildet Versucht (HAMT), sowie einige andere persistente Datenstrukturen, in reinem Ruby.
Persistente Datenstrukturen sind unveränderlich, und anstatt mutiert (verändert) die Struktur, wie durch hinzufügen oder ersetzen eines Schlüssel-Wert-paar in einem Hash, der Sie, statt zurück eine neue Daten-Struktur enthält die änderung. Der trick, mit dem beständigen unveränderlichen Datenstrukturen, ist, dass die neu zurückgegebenen Daten-Struktur wieder verwendet, wie viel von den Vorgänger als möglich.
Ich denke, zu implementieren, über hamster, die Sie verwenden würden, Ihre veränderliche hash-wrapper, die er alle liest, um den aktuellen Wert der persistenten immutable hash (dh, sollte schnell sein), während die Bewachung schreibt alles mit einer mutex und austauschen, um den neuen Wert der persistenten immutable hash nach dem schreiben.
Beispiel:
So, lassen Sie uns dieses für ein ähnliches problem beschrieben:
(gist hier)
Ich bin immer folgende Ausgänge:
Was haltet Ihr von dieser?
Diese Lösung ist ähnlich, wie könnte man dies lösen, in Scala oder Clojure, obwohl in diesen Sprachen würde man eher mit software transactional memory mit low-level-CPU-Unterstützung für die Atomare compare-and-swap-Operationen, die implementiert sind.
Bearbeiten: Es ist erwähnenswert, dass ein Grund, warum der hamster Umsetzung ist schnell ist, dass es verfügt über eine lock-free Lesen Pfad. Bitte Antworten Sie in den Kommentaren, wenn Sie Fragen haben, oder wie es funktioniert.
diese (video, pdf) ist über lock-free hash-Tabelle in Java implementiert.
spoiler: verwendet atomic Compare-And-Swap (CAS) Operationen, wenn nicht in Ruby könnte man emulieren Sie mit sperren. nicht sicher, ob das hätte keinen Vorteil gegenüber dem einfachen lock-bewacht hashtables
Nicht getestet, und einen naiven stechen um die Optimierung für liest. Es wird davon ausgegangen, dass die meisten der Zeit, wird der Wert nicht gesperrt werden. Wenn Sie es ist, die enge Schleife wird versuchen, bis es ist. Ich legte
Thread.critical
dort zu helfen, sicherzustellen, dass die gelesenen threads nicht ausgeführt werden, bis das schreiben beendet ist. Nicht sicher, ob der kritische Teil ist gebraucht, es hängt wirklich davon ab, wie gelesen-schwere meinst du, so einige benchmarking ist in Ordnung.Gibt es vielleicht ein paar anderen lese-Methoden, müssen Sie die @semaphore sperren, ich weiß nicht, ob alles andere ist implementiert #[].
ich bin mir ziemlich unklar, was damit gemeint ist. ich denke, die einfachste Implementierung ist einfach
ist zu sagen, dass die eingebauten ruby-hash ist threadsicher, wenn durch threadsicher, du meinst nicht die Luft zu sprengen, falls > 1 threads versucht, darauf zuzugreifen. dieser code wird sicher für immer
ich vermute, mit der thread-sicheren Sie wirklich meinen SÄURE - zum Beispiel ein schreiben wie hash[:key]=:val, gefolgt von einer gelesen hat[:key] zurückkehren würde :val. aber keine Menge von Tricks, mit der sperren können vorsehen, dass - die Letzte in würden immer gewinnen. zum Beispiel, sagen, Sie haben 42 Gewinde alle aktualisieren threadsicher hash - welcher Wert gelesen werden soll, die 43'rd?? sicherlich threasafe Sie nicht bedeuten, eine Art von Gesamt-Bestellung schreibt - also wenn 42 threads aktiv waren, schreiben die "richtige" Wert ist alle Recht? aber ruby ' s eingebaute Hash-funktioniert genau auf diese Weise...
vielleicht meinst du so etwas wie
in einem thread, und
würden sich nicht gegenseitig stören? ich kann mir vorstellen wollen, dass threadsicher, aber das ist noch nicht einmal sicher in einer single thread mit dem MRI-ruby (natürlich werden Sie nicht ändern können eine hash-während der Iteration über es)
so können Sie präzisieren, was du meinst mit 'threadsicher' ??
nur so ACID-Semantik wäre ein grober Sperre (sicher, das könnte eine Methode sein, nahm einen block - aber noch eine externe-Sperre).
ruby ' s thread-scheduler ist nicht nur zu planen, ein thread genau in der Mitte von einem beliebigen c-Funktion (wie die built-in hash-aref aset-Methoden), so dass diese effektiv sind threadsicher.
Leider kann ich nicht hinzufügen, einen Kommentar zu Michael Sofaer Antwort, wo er eingeführt: Klasse RWLock und Klasse LockedHash mit @reader_count etc. (die haben nicht genug karma noch)
Dass die Lösung nicht funktioniert. Gibt es einen Fehler:
in `unlock': Versuch zum entsperren eines mutex, die nicht gesperrt ist (ThreadError)
Aufgrund der logischen Fehler: wenn es Zeit zu entsperren Dinge wieder entsperren, passiert 1 extra Zeit (wegen fehlender überprüfen my_block?(). Stattdessen verstopfte es auch, wenn die Entsperrung nicht notwendig war "mein block") und so 2. freischalten auf schon freigeschaltet stumm wirft eine exception. (Ich werde einfügen vollständigen code, wie Sie diesen Fehler zu reproduzieren, die am Ende von diesem post).
Auch Michael erwähnt, "die jede Methode ist unsicher (können Mutationen durch andere threads während einer iteration)" das war wichtig für mich, so dass ich am Ende mit dieser vereinfachten Lösung, die funktioniert für alle meine Anwendungsfälle und es einfach sperren mutex auf jedem Aufruf alle hash-Methode aufgerufen, wenn aus dem anderen thread (Anrufe aus dem gleichen thread, in dessen Besitz sich das Schloss nicht blockieren, um deadlocks zu vermeiden):
Und nun das vollständige Beispiel zu veranschaulichen bzw. zu reproduzieren, den Fehler der doppelten Erschließung in Michael Sofaer Lösung:
die gibt die folgende Fehlermeldung:
Da Sie erwähnen die Hash wäre Lesen schwer, mit einer mutex-locking mit lese-und Schreibvorgänge führen würde, race conditions, die sind wohl durch liest. Wenn das ok mit Ihnen ist, dann ignorier die Antwort.
Wenn Sie wollen, zu geben, schreibt eine Priorität, ein read-write lock helfen würde. Der folgende code basiert auf einem alten c++ - Zuordnung für Betriebssysteme Klasse, so könnte nicht beste Qualität, aber gibt eine Allgemeine Vorstellung.
Dann nur wickeln Sie []= und [] in Sperre.write und lock.Lesen. Könnte sich auf die Leistung auswirken, aber die Garantie, dass schreibt, wird 'durchkommen' der liest. Nützlichkeit dieser hängt davon ab, wie Lesen schwer es eigentlich ist.