2016-06-23 16 views
1

Ich lief folgenden Code in der Konsole für mehr als 10 mal, und es ergab sich die gleiche Ausgabe, 100000.Ist Ruby Thread-Safe standardmäßig?

i = 0 
1000.times do 
Thread.start { 100.times { i += 1 } } 
end 
i 

Sollte es mir nicht anders ausgegeben, wie ich lese und i aktualisieren, mehrere Threads verwendet werden. Es hat mich wundern, ist Rubin eigentlich threadsicher standardmäßig? Wenn nicht, warum sehe ich dann immer dieselbe Ausgabe?

p.s. Wenn du sagst, dass es standardmäßig nicht Thread-sicher ist, kannst du ein einfaches Beispiel teilen, das mir ein anderes Ergebnis gibt, wenn ich in der rails-Konsole laufe?

Edit:

Mit anderen Worten, es wird über Code ausgeführt wird 1000 Threads gleichzeitig? Wenn ja, dann sollte das Ergebnis nicht 100000IMMER sein. Wenn nein, wie können mehrere Threads gleichzeitig ausgeführt werden?

Wenn ich puts hinzufüge, ändert sich die Druckreihenfolge von i. Es bedeutet, Threads verschachteln sich gegenseitig, aber laufen sie gleichzeitig?

Ich frage nicht, wie man dieses Thread-Safe macht. Ich verstehe Konzepte von mutex/locking & synchronen/asynchronen Prozess. Weil ich sie verstehe, verstehe ich die Ausgabe dieses Codes nicht.

+0

Was meinst du damit ist threadsicher, Ruby hätte keine 'Thread' Funktion wenn es nicht sicher wäre Thread mit ..? – 13aal

+0

Ich meinte, wenn ich eine lokale Variable in mehreren Threads lese und aktualisiere, sollte sich das Ergebnis nicht unterscheiden? – Abhishek

+0

Ich frage mich, wie die Reihenfolge der Anwendung der atomaren 'succ'-Operation auf' Integer' das Ergebnis beeinflussen könnte? – mudasobwa

Antwort

0

Huh !! Endlich habe ich einen Weg gefunden zu beweisen, dass es nicht immer auf irb 100000 kommen wird.

Laufen folgenden Code gab mir die Idee,

100.times do 
i = 0 
1000.times do 
Thread.start { 100.times { i += 1 } } 
end 
puts i 
end 

ich verschiedene Werte sehen, die meisten der Zeit. Meistens reicht es von 91k to 100000.

3

Kein Code ist automatisch Thread-sicher, Sie müssen arbeiten, um es threadsicher zu machen.

Insbesondere die += Operation ist eigentlich drei Operationen: lesen, erhöhen, schreiben. Wenn diese mit anderen Threads vermischt werden, kann dies zu unvorhersehbarem Verhalten führen.

die folgende Reihe von Ereignissen auf zwei Themen vor:

A  B 
------------- 
READ 
     READ 
INCR 
     INCR 
WRITE 
     WRITE 

Dies ist der einfachste Fall ist, wo Sie zwei Schritt Operationen noch haben werden, da sie beide den gleichen ursprünglichen Wert verwenden eine von ihnen zunichte gemacht wird.

In meinen Tests ist dies weniger wahrscheinlich auf einem Dual-Core-System, aber praktisch ein konstantes Problem auf vier Kern-Maschinen, weil sich viele wie zwei lose verbundene Dual-Core-Systeme mit jeweils eigenem Cache verhalten. Es ist noch ausgeprägter bei Verwendung von JRuby, wo die Threading-Unterstützung viel besser ist. Das Beispiel-Code haben Sie Ausbeuten zufällige Antworten für mich, überall von 98200 bis 99500.

auf diesen Thread zu machen sicher Sie müssen eine Mutex verwenden oder eine Atom Inkrementbetrieb aus einer Bibliothek wie Concurrent Ruby verwenden, die Ihnen die Werkzeuge geben um dies sicher zu tun.

Die Alternative besteht darin, Daten zwischen Threads zu mischen oder eine Struktur wie Queue zu verwenden, um die Kommunikation zu verwalten. Keine zwei Threads sollten jemals dasselbe Objekt ohne Mutex manipulieren.

+0

Ich verstehe Mutex & Verriegelung. Aber ich sehe keine anderen Ergebnisse. Ich bin auf Lenovo E40, Intel I5, ich bin mir ziemlich sicher, dass es mehr als 4 Threads gleichzeitig ausführen kann. – Abhishek

+0

@Abhishek Ein i5 kann beliebig viele Threads einplanen, aber nur vier werden zu einem bestimmten Zeitpunkt aktiv ausgeführt. Selbst eine einzelne Kernmaschine kann Threading-Race-Bedingungen haben, wenn der Scheduler zu einer unpassenden Zeit eintritt, wie gerade nach einem Lesen. Sie haben keine Kontrolle darüber, es sei denn, Sie haben Mutexe, um sicherzustellen, dass Sie exklusiven Zugriff auf ein bestimmtes Datenelement haben. – tadman

+0

Genau! Also, warum sehe ich immer denselben Output? – Abhishek

1

In der Informatik ein Thread einer Ausführung ist die kleinste Sequenz programmierter Befehle, die unabhängig von einem Betriebssystem-Scheduler verwaltet werden können. Ein Thread ist ein leichtgewichtiger Prozess.

irb(main):001:0> def calculate_sum(arr) 
irb(main):002:1> sleep(2) 
irb(main):003:1> sum = 0 
irb(main):004:1> arr.each do |item| 
irb(main):005:2*  sum += item 
irb(main):006:2> end 
irb(main):007:1> sum 
irb(main):008:1> end 
=> :calculate_sum 
irb(main):009:0> 
irb(main):010:0* @items1 = [12, 34, 55] 
=> [12, 34, 55] 
irb(main):011:0> @items2 = [45, 90, 2] 
=> [45, 90, 2] 
irb(main):012:0> @items3 = [99, 22, 31] 
=> [99, 22, 31] 
irb(main):013:0> 
irb(main):014:0* threads = (1..3).map do |i| 
irb(main):015:1* Thread.new(i) do |i| 
irb(main):016:2*  items = instance_variable_get("@items#{i}") 
irb(main):017:2>  puts "items#{i} = #{calculate_sum(items)}" 
irb(main):018:2> end 
irb(main):019:1> end 
=> [#<Thread:[email protected](irb):15 run>, #<Thread:[email protected](irb):15 run>, #<Thread:[email protected](irb):15 run>] 
irb(main):020:0> threads.each {|t| t.join} 
items3 = 152 
items2 = 137 
items1 = 101 
=> [#<Thread:[email protected](irb):15 dead>, #<Thread:[email protected](irb):15 dead>, #<Thread:[email protected](irb):15 dead>] 
irb(main):021:0> 

Dies ist ein grundlegendes Beispiel für das Einfädeln eines Prozesses in Ruby. Sie haben die Hauptmethode calculate_sum, die ein Array als Argument @item1, @item2, @item3 nimmt. Von dort aus machen Sie drei Threads threads = (1..3) ordnen sie in ihre eigene Variable .map do |i| und starten Sie eine neue Thread Instanz mit der Variablen, die der Thread zugeordnet wurde, Thread.start(i).

Von hier aus erstellen Sie eine Elementvariable, die der Instanzvariable items = instance_variable_get(<object>) entspricht. Geben Sie das Ergebnis der Berechnungen aus. Wie Sie sehen können, beginnen die Threads gleichzeitig => [#<Thread:[email protected](irb):15 run>, #<Thread:[email protected](irb):15 run>, #<Thread:[email protected](irb):15 run>]. Die Threads werden alle ausgeführt, indem jeder Thread aufgerufen und mit ihnen verbunden wird threads.each {|t| t,join}.

Der letzte Abschnitt ist der wichtigste, die Threads werden alle ausgeführt und sterben gleichzeitig, aber wenn ein Thread einen sehr langen Prozess hat, muss der Thread enden, bevor das Programm beendet wird. Beispiel:

irb(main):023:0> Thread.new do 
irb(main):024:1* puts t 
irb(main):025:1> Thread.new do 
irb(main):026:2*  sleep(5) 
irb(main):027:2>  puts h 
irb(main):028:2> end 
irb(main):029:1> end 
=> #<Thread:[email protected](irb):23 run> 
irb(main):030:0> hello 
goodbye 

Der zweite Thread wird nie beendet, daher wird der Prozess solange ausgeführt, bis Sie die Ausführung abbrechen.

Im Hauptbeispiel hat das Ende => [#<Thread:[email protected](irb):15 dead>, #<Thread:[email protected](irb):15 dead>, #<Thread:[email protected](irb):15 dead>], weil alle Threads den Prozess beenden und sofort beenden. Damit mein Prozess fertig ist, müssten Sie einen 10 für den zweiten Thread bereitstellen.

Ich hoffe, das beantwortet Ihre Fragen.

0

Leider hat Ruby kein offiziell spezifiziertes Speichermodell wie Java seit Java 5 oder C++ seit C++ 11.

Tatsächlich hat Ruby wirklich keine offizielle Spezifikation überhaupt, obwohl es mehrere Versuche gab, haben alle das gleiche Problem, dass die Designer von Ruby sie nicht tatsächlich verwenden. Also, die einzige Spezifikation, die Ruby hat, ist im Grunde "was auch immer YARV tut". (Und zum Beispiel spezifiziert die ISO-Ruby-Sprachspezifikation einfach nicht die Thread-Klasse, wodurch das Problem insgesamt verschoben wird.)

ABER !!! Bei Parallelität ist dies im Grunde unbrauchbar, weil YARV Threads nicht parallel ausführen kann, so dass viele Parallelitätsprobleme in YARV einfach nicht auftreten und die Core-Bibliothek daher nicht vor diesen Problemen schützt! Wenn wir jedoch sagen würden, dass die Nebenläufigkeitssemantik von Ruby das ist, was YARV auch tut, wird die Frage nun: Ist die Tatsache, dass wir keinen Parallelismus in der Semantik haben können? Ist die Tatsache, dass die Kernbibliotheken nicht geschützt sind, Teil der Semantik?

Das ist ein Kampf, dass Implementierungen wie JRuby, Rubinius, IronRuby, MacRuby usw., die Threads haben, die parallel laufen können, konfrontiert sind. Und sie arbeiten immer noch daran, die Antworten herauszufinden.

Also, die tl; dr Antwort auf Ihre Frage ist: Wir wissen nicht, ob Ruby-Thread-sicher ist, weil wir nicht wissen, was das Einfädeln Semantik von Ruby sind.

Es ist ziemlich häufig für Multithread-Programme, die gut auf YARV funktionieren, um zum Beispiel auf JRuby zu brechen, aber wieder, ist es die Schuld des Programms oder JRuby's? Wir können es nicht sagen, weil wir keine Spezifikation haben, die uns sagt, wie das Multi-Thread-Verhalten einer Ruby-Implementierung aussehen sollte. Wir könnten den einfachen Ausweg nehmen und sagen, nun, Ruby ist was auch immer YARV macht, und wenn das Programm auf YARV läuft, müssen wir JRuby ändern, damit das Programm auch auf YARV funktioniert. Parallelität ist jedoch einer der Hauptgründe, warum die Leute sich für JRuby entscheiden, das ist einfach nicht machbar.

Verwandte Themen