Ab Rails 4 müsste standardmäßig alles in einer Thread-Umgebung ausgeführt werden. Dies bedeutet, dass der gesamte Code, den wir schreiben, UND ALLE Edelsteine, die wir verwenden, vorhanden sein müssenthreadsafe
Daher habe ich einige Fragen dazu:
- Was ist in Rubin / Schienen NICHT fadensicher? Vs Was ist in Rubin / Schienen fadensicher?
- Gibt es eine Liste der Edelsteine , die ist bekannt THREAD oder umgekehrt zu sein?
- Gibt es eine Liste gängiger Codemuster, die KEINE threadsicheren Beispiele sind
@result ||= some_method
? - Sind die Datenstrukturen in Ruby Lang Core wie
Hash
etc threadsicher? - Bei der MRT, bei der ein
GVL
/GIL
was bedeutet, dass nur 1IO
Rubinfaden gleichzeitig ausgeführt werden kann , wirkt sich die threadsichere Änderung auf uns aus?
ruby
multithreading
concurrency
thread-safety
ruby-on-rails-4
CuriousMind
quelle
quelle
Antworten:
Keine der Kerndatenstrukturen ist threadsicher. Das einzige, von dem ich weiß, dass es mit Ruby geliefert wird, ist die Warteschlangenimplementierung in der Standardbibliothek (
require 'thread'; q = Queue.new
).Die GIL von MRI rettet uns nicht vor Fragen der Thread-Sicherheit. Es wird nur sichergestellt, dass nicht zwei Threads Ruby-Code gleichzeitig ausführen können , dh auf zwei verschiedenen CPUs genau zur gleichen Zeit. Threads können weiterhin an jedem Punkt in Ihrem Code angehalten und fortgesetzt werden. Wenn Sie Code schreiben,
@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
z. B. eine gemeinsam genutzte Variable aus mehreren Threads mutieren, ist der Wert der gemeinsam genutzten Variablen danach nicht deterministisch. Die GIL ist mehr oder weniger eine Simulation eines einzelnen Kernsystems. Sie ändert nichts an den grundlegenden Problemen beim Schreiben korrekter gleichzeitiger Programme.Selbst wenn die MRT wie Node.js Single-Threaded gewesen wäre, müssten Sie immer noch über Parallelität nachdenken. Das Beispiel mit der inkrementierten Variablen würde gut funktionieren, aber Sie können immer noch Rennbedingungen erhalten, bei denen die Dinge in nicht deterministischer Reihenfolge ablaufen und ein Rückruf das Ergebnis eines anderen blockiert. Asynchrone Systeme mit einem Thread sind leichter zu verstehen, aber nicht frei von Parallelitätsproblemen. Stellen Sie sich eine Anwendung mit mehreren Benutzern vor: Wenn zwei Benutzer mehr oder weniger gleichzeitig auf "Bearbeiten" für einen Stapelüberlauf-Beitrag klicken, müssen Sie einige Zeit damit verbringen, den Beitrag zu bearbeiten, und dann auf "Speichern" klicken, dessen Änderungen später von einem dritten Benutzer angezeigt werden den gleichen Beitrag lesen?
In Ruby ist, wie in den meisten anderen gleichzeitigen Laufzeiten, alles, was mehr als eine Operation ist, nicht threadsicher.
@n += 1
ist nicht threadsicher, da es sich um mehrere Operationen handelt.@n = 1
ist threadsicher, weil es sich um eine Operation handelt (es sind viele Operationen unter der Haube, und ich würde wahrscheinlich in Schwierigkeiten geraten, wenn ich versuchen würde, detailliert zu beschreiben, warum es "threadsicher" ist, aber am Ende erhalten Sie keine inkonsistenten Ergebnisse aus Zuweisungen ).@n ||= 1
, ist nicht und keine andere Kurzoperation + Zuweisung ist auch nicht. Ein Fehler, den ich oft gemacht habe, ist das Schreibenreturn unless @started; @started = true
, das überhaupt nicht threadsicher ist.Ich kenne keine maßgebliche Liste von thread-sicheren und nicht thread-sicheren Anweisungen für Ruby, aber es gibt eine einfache Faustregel: Wenn ein Ausdruck nur eine (nebenwirkungsfreie) Operation ausführt, ist er wahrscheinlich thread-sicher. Zum Beispiel:
a + b
ist in Ordnung,a = b
ist auch in Ordnung unda.foo(b)
ist in Ordnung, wenn die Methode nebenwirkungsfreifoo
ist (da fast alles in Ruby ein Methodenaufruf ist, in vielen Fällen sogar eine Zuweisung, gilt dies auch für die anderen Beispiele). Nebenwirkungen bedeuten in diesem Zusammenhang Dinge, die den Zustand ändern.def foo(x); @x = x; end
ist nicht nebenwirkungsfrei.Eines der schwierigsten Dinge beim Schreiben von thread-sicherem Code in Ruby ist, dass alle Kerndatenstrukturen, einschließlich Array, Hash und String, veränderbar sind. Es ist sehr leicht, versehentlich ein Stück Ihres Zustands zu verlieren, und wenn dieses Stück veränderlich ist, können die Dinge wirklich durcheinander geraten. Betrachten Sie den folgenden Code:
Eine Instanz dieser Klasse kann von Threads gemeinsam genutzt werden und sie können sicher Dinge hinzufügen, aber es gibt einen Parallelitätsfehler (es ist nicht der einzige): Der interne Status des Objekts leckt durch den
stuff
Accessor. Es ist nicht nur aus Sicht der Kapselung problematisch, sondern eröffnet auch eine Dose Parallelitätswürmer. Vielleicht nimmt jemand dieses Array und gibt es an einen anderen Ort weiter, und dieser Code glaubt wiederum, dass er dieses Array jetzt besitzt und damit machen kann, was er will.Ein weiteres klassisches Ruby-Beispiel ist folgendes:
find_stuff
funktioniert gut, wenn es zum ersten Mal verwendet wird, gibt aber beim zweiten Mal etwas anderes zurück. Warum? Dieload_things
Methode glaubt zufällig, dass sie den an sie übergebenen Options-Hash besitzt, und tut dies auchcolor = options.delete(:color)
. Jetzt hat dieSTANDARD_OPTIONS
Konstante nicht mehr den gleichen Wert. Konstanten sind nur in dem, worauf sie verweisen, konstant, sie garantieren nicht die Konstanz der Datenstrukturen, auf die sie sich beziehen. Stellen Sie sich vor, was passieren würde, wenn dieser Code gleichzeitig ausgeführt würde.Wenn Sie einen gemeinsamen veränderlichen Status vermeiden (z. B. Instanzvariablen in Objekten, auf die mehrere Threads zugreifen, Datenstrukturen wie Hashes und Arrays, auf die mehrere Threads zugreifen), ist die Thread-Sicherheit nicht so schwierig. Versuchen Sie, die Teile Ihrer Anwendung zu minimieren, auf die gleichzeitig zugegriffen wird, und konzentrieren Sie Ihre Bemühungen dort. IIRC: In einer Rails-Anwendung wird für jede Anforderung ein neues Controller-Objekt erstellt, sodass es nur von einem einzelnen Thread verwendet wird. Dies gilt auch für alle Modellobjekte, die Sie von diesem Controller erstellen. Rails empfiehlt jedoch auch die Verwendung globaler Variablen (
User.find(...)
verwendet die globale VariableUser
Sie können sich das nur als eine Klasse vorstellen, und es ist eine Klasse, aber es ist auch ein Namespace für globale Variablen. Einige davon sind sicher, weil sie schreibgeschützt sind, aber manchmal speichern Sie Dinge in diesen globalen Variablen, weil es ist bequem. Seien Sie sehr vorsichtig, wenn Sie alles verwenden, auf das global zugegriffen werden kann.Es ist schon seit einiger Zeit möglich, Rails in Thread-Umgebungen auszuführen. Ohne Rails-Experte würde ich immer noch sagen, dass Sie sich keine Sorgen um die Thread-Sicherheit machen müssen, wenn es um Rails selbst geht. Sie können weiterhin Rails-Anwendungen erstellen, die nicht threadsicher sind, indem Sie einige der oben genannten Schritte ausführen. Wenn es darum geht, nehmen andere Edelsteine an, dass sie nicht threadsicher sind, es sei denn, sie sagen, dass sie es sind, und wenn sie sagen, dass sie es nicht sind, und schauen Sie sich ihren Code an (aber nur, weil Sie sehen, dass sie Dinge wie gehen
@n ||= 1
bedeutet nicht, dass sie nicht threadsicher sind, das ist eine absolut legitime Sache, die im richtigen Kontext zu tun ist. Sie sollten stattdessen nach Dingen wie dem veränderlichen Status in globalen Variablen suchen, wie sie mit veränderlichen Objekten umgehen, die an ihre Methoden übergeben werden, und insbesondere wie sie behandelt Options-Hashes).Schließlich ist es eine transitive Eigenschaft, Thread-unsicher zu sein. Alles, was etwas verwendet, das nicht threadsicher ist, ist selbst nicht threadsicher.
quelle
STANDARD_OPTIONS = {...}.freeze
, um auf flachen Mutationen zu erhöhen@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
[...] schreiben , ist der Wert der gemeinsam genutzten Variablen danach nicht deterministisch." - Wissen Sie, ob dies zwischen Ruby-Versionen unterschiedlich ist? Zum Beispiel ergibt das Ausführen Ihres Codes auf 1.8 unterschiedliche Werte von@n
, aber auf 1.9 und höher scheint es konsistent@n
gleich 300 zu sein.Zusätzlich zu Theos Antwort möchte ich einige Problembereiche hinzufügen, auf die Sie in Rails besonders achten sollten, wenn Sie zu config.threadsafe wechseln!
Klassenvariablen :
@@i_exist_across_threads
ENV :
ENV['DONT_CHANGE_ME']
Themen :
Thread.start
quelle
Dies ist nicht 100% korrekt. Thread-sichere Schienen sind standardmäßig aktiviert. Wenn Sie auf einem App-Server mit mehreren Prozessen wie Passenger (Community) oder Unicorn bereitstellen, gibt es überhaupt keinen Unterschied. Diese Änderung betrifft Sie nur, wenn Sie in einer Multithread-Umgebung wie Puma oder Passenger Enterprise> 4.0 bereitstellen
Wenn Sie in der Vergangenheit auf einem Multithread-App-Server bereitstellen wollten, mussten Sie config.threadsafe aktivieren , was jetzt Standard ist, da alles entweder keine Auswirkungen hatte oder auch auf eine Rails-App angewendet wurde, die in einem einzigen Prozess ausgeführt wurde ( Prooflink ).
Wenn Sie jedoch alle Rails 4- Streaming- Vorteile und andere Echtzeitfunktionen der Multithread-Bereitstellung nutzen möchten , ist dieser Artikel möglicherweise interessant. Wie @Theo traurig ist, müssen Sie für eine Rails-App während einer Anforderung lediglich den mutierenden statischen Status weglassen. Obwohl dies eine einfache Übung ist, können Sie sich leider nicht für jeden Edelstein, den Sie finden, sicher sein. Soweit ich mich erinnere, hatte Charles Oliver Nutter vom JRuby-Projekt in diesem Podcast einige Tipps dazu .
Und wenn Sie eine reine gleichzeitige Ruby-Programmierung schreiben möchten, bei der Sie einige Datenstrukturen benötigen, auf die mehr als ein Thread zugreift, ist das Juwel thread_safe möglicherweise hilfreich.
quelle