81

starting from Rails 4, müsste alles standardmäßig in einer Umgebung mit Threads ausgeführt werden. Was dies bedeutet, den gesamten Code ist, dass wir UNDALLE die Edelsteine ​​wir threadsafeWie kann man wissen, was NICHT in Ruby Thread-sicher ist?

zu verwenden schreiben erforderlich, so, ich habe einige Fragen zu diesem Thema:

  1. was Thread-sicher-NICHT in Rubin/Rails? Vs Was ist in Rubinen/Schienen threadsicher?
  2. Gibt es eine Liste von Edelsteinen, die ist bekannt, Threadssafe oder umgekehrt zu sein?
  3. gibt es Liste der gängigen Muster des Codes, die nicht Threadssafe Beispiel @result ||= some_method sind?
  4. Sind die Datenstrukturen in Ruby lang Kern wie Hash usw. threadsafe?
  5. Auf MRI, wo gibt es eine GVL/GIL was bedeutet, dass nur 1 Ruby Thread auf einmal ausgeführt werden kann außer IO, wirkt sich die threadsafee Änderung uns?
+2

Sind Sie sicher, dass der gesamte Code und alle Edelsteine ​​müssen threadsicher sein? Was die Release Notes sagen, ist, dass Rails selbst threadsicher sein wird, nicht dass alles, was damit verwendet wird, – enthrops

+0

ist. Multithread-Tests wären das schlimmstmögliche Threadsafe-Risiko. Wenn Sie den Wert einer Umgebungsvariablen um Ihren Testfall herum ändern müssen, sind Sie nicht sofort threadsafe. Wie würdest du das umgehen? Und ja, alle Edelsteine ​​müssen threadsicher sein. –

Antwort

92

Keine der Kerndatenstrukturen sind Thread-sicher. Der einzige, den ich kenne, der mit Ruby ausgeliefert wird, ist die Queue-Implementierung in der Standard-Bibliothek (require 'thread'; q = Queue.new).

MRI's GIL rettet uns nicht vor Threadsicherheitsproblemen. Es stellt nur sicher, dass zwei Threads Ruby-Code zur gleichen Zeit, d. H. Auf zwei verschiedenen CPUs zur gleichen Zeit nicht ausführen können. Threads können an einem beliebigen Punkt in Ihrem Code pausiert und fortgesetzt werden. Wenn Sie Code wie @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } } schreiben, z. Wenn eine freigegebene Variable aus mehreren Threads mutiert wird, ist der Wert der gemeinsam genutzten Variablen danach nicht deterministisch. Die GIL ist mehr oder weniger eine Simulation eines einzelnen Kernsystems, sie ändert nicht die grundlegenden Probleme beim Schreiben korrekter konkurrierender Programme.

Selbst wenn die MRI wie Node.js single-threaded gewesen wäre, müssten Sie immer noch über Nebenläufigkeit nachdenken. Das Beispiel mit der inkrementierten Variablen würde gut funktionieren, aber Sie können immer noch Race-Bedingungen erhalten, bei denen Dinge in nicht-deterministischer Reihenfolge passieren und ein Callback das Ergebnis eines anderen Clobbers überlagert. Asynchrone Single-Threaded-Systeme sind einfacher zu verstehen, aber sie sind nicht frei von Nebenläufigkeitsproblemen. Denken Sie nur an eine Anwendung mit mehreren Benutzern: Wenn zwei Benutzer gleichzeitig in einem Stapelüberlauf-Post bearbeiten, verbringen Sie etwas Zeit mit dem Bearbeiten des Posts und drücken Sie dann auf Speichern, dessen Änderungen später von einem dritten Benutzer gesehen werden Lesen Sie den gleichen Beitrag?

In Ruby, wie in den meisten anderen gleichzeitigen Laufzeiten, ist alles, was mehr als eine Operation ist, nicht Thread-sicher. @n += 1 ist nicht threadsicher, da es sich um mehrere Operationen handelt. @n = 1 ist Thread-sicher, weil es eine Operation ist (es gibt viele Operationen unter der Haube, und ich würde wahrscheinlich in Schwierigkeiten geraten, wenn ich versuchen würde zu beschreiben, warum es "thread-safe" im Detail ist, aber am Ende werden Sie keine inkonsistenten Ergebnisse erhalten Zuordnungen). @n ||= 1, ist nicht und keine andere Kurzschrift Operation + Zuweisung ist entweder. Ein Fehler, den ich oft gemacht habe, ist return unless @started; @started = true zu schreiben, was überhaupt nicht threadsicher ist.

Ich kenne keine autoritative Liste von threadsicheren und nicht threadsicheren Anweisungen für Ruby, aber es gibt eine einfache Faustregel: Wenn ein Ausdruck nur eine (side-effect free) Operation ausführt, ist es wahrscheinlich fadensicher.Zum Beispiel: a + b ok ist, a = b auch in Ordnung ist, und a.foo(b) ist in Ordnung, , wenn die Methode foo ist nebenwirkungsfreie (da gerade über alles in Ruby ein Methodenaufruf ist, auch in vielen Fällen Zuordnung, geht dies für die andere Beispiele auch). Nebenwirkungen bedeutet in diesem Zusammenhang Dinge, die den Zustand ändern. def foo(x); @x = x; end ist nicht Nebenwirkung frei.

Eines der schwierigsten Dinge beim Schreiben von threadsicherem Code in Ruby ist, dass alle Kerndatenstrukturen, einschließlich Array, Hash und String, änderbar sind. Es ist sehr leicht, versehentlich ein Stück deines Zustands zu verlieren, und wenn dieses Stück veränderbar ist, können die Dinge wirklich vermasselt werden. Betrachten Sie den folgenden Code ein:

class Thing 
    attr_reader :stuff 

    def initialize(initial_stuff) 
    @stuff = initial_stuff 
    @state_lock = Mutex.new 
    end 

    def add(item) 
    @state_lock.synchronize do 
     @stuff << item 
    end 
    end 
end 

Eine Instanz dieser Klasse kann zwischen Threads gemeinsam genutzt werden, und sie können sicher hinzufügen Dinge, aber es gibt einen gemeinsamen Zugriff Fehler (es ist nicht der einzige): der interne Zustand des Objekts Lecks durch den stuff Accessor. Abgesehen davon, dass es aus der Verkapselungsperspektive problematisch ist, öffnet es auch eine Dose von Parallelwürmern. Vielleicht nimmt jemand dieses Array und gibt es an einen anderen Ort weiter, und dieser Code wiederum glaubt, dass er jetzt dieses Array besitzt und damit machen kann, was er will.

Ein weiteres klassisches Ruby-Beispiel ist dies:

STANDARD_OPTIONS = {:color => 'red', :count => 10} 

def find_stuff 
    @some_service.load_things('stuff', STANDARD_OPTIONS) 
end 

find_stuff Werke zum ersten Mal eine Fein es verwendet wird, liefert aber etwas anderes das zweite Mal. Warum? Die Methode load_things geht davon aus, dass sie den übergebenen Options-Hash besitzt und color = options.delete(:color). Jetzt hat die Konstante STANDARD_OPTIONS 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. Denken Sie nur darüber nach, was passieren würde, wenn dieser Code gleichzeitig ausgeführt würde.

Wenn Sie den gemeinsamen veränderbaren Zustand vermeiden (z. B. Instanzvariablen in Objekten, auf die mehrere Threads zugreifen, Datenstrukturen wie Hashes und Arrays, auf die mehrere Threads zugreifen), ist die Threadsicherheit 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 ein neues Controller-Objekt für jede Anfrage erstellt, so dass es nur von einem einzelnen Thread verwendet wird, und dasselbe gilt für alle Modellobjekte, die Sie von diesem Controller erstellen. Rails ermutigt jedoch auch die Verwendung globaler Variablen (User.find(...) verwendet die globale Variable User, Sie können es sich 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 nur gelesen werden, aber manchmal speichern Sie Dinge in diesen globalen Variablen, weil es bequem ist. Sei vorsichtig, wenn du etwas verwendest, das global zugänglich ist.

Es war schon eine ganze Weile möglich, Rails in Threaded Umgebungen laufen zu lassen, also ohne ein Rails-Experte zu sein, würde ich immer noch sagen, dass man sich bei Rails nicht um Thread-Sicherheit kümmern muss selbst. Sie können immer noch Rails-Anwendungen erstellen, die nicht Thread-sicher sind, indem Sie einige der oben erwähnten Dinge tun. Wenn es darum geht andere Edelsteine ​​anzunehmen, dass sie nicht threadsicher sind, es sei denn sie sagen, dass sie es sind, und wenn sie sagen, dass sie davon ausgehen, dass sie es nicht sind, und ihren Code durchsehen (aber nur weil man sieht, dass sie Dinge wie @n ||= 1 tun nicht bedeuten, dass sie nicht threadsicher sind, das ist eine vollkommen legitime Sache im richtigen Kontext - Sie sollten stattdessen nach Dingen wie veränderbaren Status in globalen Variablen suchen, wie sie änderbare Objekte behandelt, die an ihre Methoden übergeben werden, und vor allem wie sie behandelt Optionen Hashes).

Schließlich ist Threads unsicher eine transitive Eigenschaft. Alles, was etwas verwendet, das nicht threadsicher ist, ist selbst nicht threadsicher.

+0

Große Antwort. Wenn man bedenkt, dass eine typische Rails-App Multi-Prozess ist (wie Sie beschrieben haben, viele verschiedene Benutzer greifen auf dieselbe App zu), frage ich mich, was das * Grenzrisiko * von Threads für das Concurrency-Modell ist ... Mit anderen Worten, wie viel mehr " gefährlich "ist es im Thread-Modus zu laufen, wenn Sie bereits mit einigen Nebenläufigkeiten über Prozesse zu tun haben? – gingerlime

+1

@Theo Danke eine Tonne. Dieses ständige Zeug ist eine große Bombe. Es ist nicht einmal prozesssicher.Wenn die Konstante in einer Anforderung geändert wird, bewirkt dies, dass die späteren Anforderungen die geänderte Konstante auch in einem einzelnen Thread anzeigen. Ruby-Konstanten sind seltsam – rubish

+3

Tun 'STANDARD_OPTIONS = {...} .Frost', um auf oberflächliche Mutationen zu erhöhen – glebm

9

Zusätzlich zu Theos Antwort, würde ich ein paar Problembereiche hinzufügen, die speziell in Rails gesucht werden, wenn Sie zu config.threadsafe wechseln!

  • Klassenvariablen:

    @@i_exist_across_threads

  • ENV:

    ENV['DONT_CHANGE_ME']

  • Themen:

    Thread.start

7

von Rails beginnend 4, müßte alles in threaded Umgebung standardmäßig korrekt

Dies ist nicht zu 100% laufen. Threadsafe Rails ist standardmäßig aktiviert. Wenn Sie immer noch einen Multiprozess-App-Server wie Passagier (Community) oder Einhorn bereitstellen, wird es überhaupt keinen Unterschied geben. Diese Änderung betrifft Sie nur, wenn Sie auf einem Multithread-Umgebung wie Puma oder Passagier Unternehmen einsetzen> 4.0

In der Vergangenheit, wenn Sie auf einem Multi-Threaded-App-Server Sie hatte einzuschalten Config einsetzen wollte. threadsafe, das jetzt standardmäßig ist, weil alles, was es tat, entweder keine Effekte hatte oder auch auf eine Rails-App angewendet wurde, die in einem einzigen Prozess lief (Prooflink).

Aber wenn Sie nicht möchten, alle Schienen 4 streaming Vorteile und andere Echtzeit-Sachen der multithreaded Einsatz dann vielleicht werden Sie this Artikel interessant zu finden. Wie bei @Theo traurig, müssen Sie bei einer Rails-App einfach während einer Anfrage den mutierenden statischen Zustand weglassen. Während dies eine einfache Praxis zu folgen ist, können Sie nicht sicher für jeden Edelstein, den Sie finden. Soweit ich mich erinnere, hatte Charles Oliver Nutter vom Jruby-Projekt einige Tipps dazu in this Podcast.

Und wenn Sie wollen eine reine gleichzeitige Rubin Programmierung schreiben, wo Sie einige Datenstrukturen benötigen würden, die um mehr als einen Thread zugegriffen werden vielleicht haben Sie die thread_safe Juwel nützlich finden

Verwandte Themen