Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Erläuterung von Parallelität, Parallelität und globaler Sperrcode-Freigabe in Ruby

Detaillierte Erläuterung von Parallelität, Parallelität und globaler Sperrcode-Freigabe in Ruby

巴扎黑
巴扎黑Original
2017-09-13 09:44:481617Durchsuche

Ich lerne kürzlich Ruby und möchte zusammenfassen und teilen, was ich gelernt habe. Der folgende Artikel führt Sie hauptsächlich in die relevanten Informationen zu Parallelität und globalen Sperren in Ruby ein In Not können Sie sich darauf beziehen, schauen wir uns unten um.

Vorwort

In diesem Artikel werden hauptsächlich relevante Inhalte zu Ruby-Parallelität und globalen Sperren vorgestellt und als Referenz und Studium weitergegeben. Unten gibt es nicht viel zu sagen, werfen wir einen Blick auf die ausführliche Einführung.

Parallelität und Parallelität

Während der Entwicklung kommen wir oft mit zwei Konzepten in Kontakt: Parallelität und Parallelität, über die fast alle reden In Artikeln über Parallelität und Parallelität wird eines erwähnt: Parallelität ist nicht gleichbedeutend mit Parallelität.

  • Parallelität: Der Koch hat die von zwei Gästen gleichzeitig bestellten Menüs erhalten . Muss abgearbeitet werden.

  • Sequentielle Ausführung: Wenn es nur einen Koch gibt, dann kann er nur ein Menü nach dem anderen fertigstellen.

  • Parallele Ausführung: Wenn es zwei Köche gibt, können sie parallel kochen und zwei Personen kochen zusammen

Wenn wir dieses Beispiel auf unsere Webentwicklung erweitern, kann es so verstanden werden:

  • Parallelität: Der Server hat gleichzeitig von zwei Clients initiierte Anforderungen empfangen

  • Sequentielle Ausführung: Der Server hat nur einen Prozess (Thread). Verarbeiten Sie die Anfrage und schließen Sie die erste ab. Eine Anfrage kann die zweite Anfrage abschließen, daher muss die zweite Anfrage warten.

  • Parallele Ausführung: Der Server verfügt über zwei Prozesse (Threads), um die Anfrage zu verarbeiten , und beide Anfragen können eine Antwort ohne Probleme mit der Reihenfolge erhalten.

Wie können wir gemäß dem oben beschriebenen Beispiel ein solches gleichzeitiges Verhalten in Ruby simulieren? Code:

1. Sequentielle Ausführung:

Simulieren Sie den Vorgang, wenn nur ein Thread vorhanden ist.


require 'benchmark'

def f1
 puts "sleep 3 seconds in f1\n"
 sleep 3
end

def f2
 puts "sleep 2 seconds in f2\n"
 sleep 2 
end

Benchmark.bm do |b|
 b.report do
 f1
 f2
 end 
end
## 
## user  system  total  real
## sleep 3 seconds in f1
## sleep 2 seconds in f2
## 0.000000 0.000000 0.000000 ( 5.009620)

Der obige Code ist sehr einfach, um zeitaufwändige Vorgänge zu simulieren.

2. Parallele Ausführung >
Simulation des Multithreading-Vorgangs



Wir haben festgestellt, dass der Zeitaufwand bei Multithreading dem ähnlich ist von f1, was wir erwartet haben, können Threads mit Multithreading parallelisiert werden.
# 接上述代码
Benchmark.bm do |b|
 b.report do
 threads = []
 threads << Thread.new { f1 }
 threads << Thread.new { f2 }
 threads.each(&:join)
 end 
end
##
## user  system  total  real
## sleep 3 seconds in f1
## sleep 2 seconds in f2
## 0.000000 0.000000 0.000000 ( 3.005115)


Rubys Multithreading kann mit IO Block umgehen weiterhin ausführen, wodurch die Gesamtverarbeitungszeit erheblich verkürzt wird.

Threads in Ruby
Das obige Codebeispiel verwendet die Thread-Klasse in Ruby, das einfach in Ruby geschrieben werden kann. Ein Multithread-Programm der Thread-Klasse ist eine einfache und effektive Möglichkeit, Parallelität in Ihrem Code zu erreichen.


Im Folgenden wird ein Parallelitätsszenario beschrieben


Die Erstellung des Threads erfolgt nicht blockierend, sodass der Text sofort ausgegeben werden kann. Dadurch wird ein gleichzeitiges Verhalten simuliert Durch Blockieren kann Multithreading Parallelität erreichen.
 def thread_test
 time = Time.now
 threads = 3.times.map do 
  Thread.new do
  sleep 3 
  end
 end
 puts "不用等3秒就可以看到我:#{Time.now - time}"
 threads.map(&:join)
 puts "现在需要等3秒才可以看到我:#{Time.now - time}"
 end
 test
 ## 不用等3秒就可以看到我:8.6e-05
 ## 现在需要等3秒才可以看到我:3.003699

Haben wir zu diesem Zeitpunkt die Parallelität abgeschlossen?


Leider in meiner obigen Beschreibung Wir Ich habe gerade erwähnt, dass wir Parallelität in einer nicht blockierenden Situation simulieren können:


Wie wir hier sehen können, auch wenn wir Dieselbe Aufgabe wird in 4 parallele Threads aufgeteilt, aber die Zeit wird nicht verkürzt. Warum ist das so?
require &#39;benchmark&#39;
def multiple_threads
 count = 0
 threads = 4.times.map do 
 Thread.new do
  2500000.times { count += 1}
 end
 end
 threads.map(&:join)
end

def single_threads
 time = Time.now
 count = 0
 Thread.new do
 10000000.times { count += 1}
 end.join
end

Benchmark.bm do |b|
 b.report { multiple_threads }
 b.report { single_threads }
end
##  user  system  total  real
## 0.600000 0.010000 0.610000 ( 0.607230)
## 0.610000 0.000000 0.610000 ( 0.623237)


Wegen der Existenz von Global Lock (GIL)! ! !

Globale Sperre
Der Rubin, den wir normalerweise verwenden, verwendet einen Mechanismus namens GIL.

Auch wenn Wir möchten mehrere Threads verwenden, um Codeparallelität zu erreichen. Aufgrund der Existenz dieser globalen Sperre kann jeweils nur ein Thread den Code ausführen. Dies hängt von der Implementierung des zugrunde liegenden Betriebssystems ab.


Selbst wenn wir mehrere CPUs haben, bietet es nur ein paar weitere Optionen für die Ausführung jedes Threads.

In unserem obigen Code kann jeweils nur ein Thread count += 1 ausführen.

Ruby-Multithreading kann Multi-Core-CPUs nicht wiederverwenden und die insgesamt aufgewendete Zeit wird dadurch nicht verkürzt Bei Verwendung von Multithreading kann sich der Zeitaufwand aufgrund der Auswirkungen des Thread-Wechsels leicht erhöhen.

Aber als wir vorher geschlafen haben, haben wir eindeutig Parallelität erreicht!

Dies ist das erweiterte Design von Ruby – alle Blockierungsvorgänge können parallelisiert werden, einschließlich Lesen und Schreiben von Dateien und Netzwerkanfragen


Die Das Programm wird bei Netzwerkanfragen blockiert, und diese Blockierungen können bei der Ausführung in Ruby parallelisiert werden, sodass der Zeitverbrauch erheblich verkürzt wird.
require &#39;benchmark&#39;
require &#39;net/http&#39;

# 模拟网络请求
def multiple_threads
 uri = URI("http://www.baidu.com")
 threads = 4.times.map do 
 Thread.new do
  25.times { Net::HTTP.get(uri) }
 end
 end
 threads.map(&:join)
end

def single_threads
 uri = URI("http://www.baidu.com")
 Thread.new do
 100.times { Net::HTTP.get(uri) }
 end.join
end

Benchmark.bm do |b|
 b.report { multiple_threads }
 b.report { single_threads }
end

 user  system  total  real
0.240000 0.110000 0.350000 ( 3.659640)
0.270000 0.120000 0.390000 ( 14.167703)

Gedanken zu GIL
Bedeutet die Existenz dieser GIL-Sperre also, dass unser Code threadsicher ist?


Leider nein, GIL wechselt bei bestimmten Arbeiten zu einem anderen Arbeitsthread Punkte während der Ruby-Ausführung. Wenn einige Klassenvariablen gemeinsam genutzt werden, kann dies zu Problemen führen.

那么, GIL 在 ruby代码的执行中什么时候会切换到另外一个线程去工作呢?

有几个明确的工作点:

  • 方法的调用和方法的返回, 在这两个地方都会检查一下当前线程的gil的锁是否超时,是否要调度到另外线程去工作

  • 所有io相关的操作, 也会释放gil的锁让其它线程来工作

  • 在c扩展的代码中手动释放gil的锁

  • 还有一个比较难理解, 就是ruby stack 进入 c stack的时候也会触发gil的检测

一个例子


@a = 1
r = []
10.times do |e|

Thread.new {
 @c = 1
 @c += @a
 r << [e, @c]
}
end
r
## [[3, 2], [1, 2], [2, 2], [0, 2], [5, 2], [6, 2], [7, 2], [8, 2], [9, 2], [4, 2]]

上述中r 里 虽然e的前后顺序不一样, 但是@c的值始终保持为 2 ,即每个线程时都能保留好当前的 @c 的值.没有线程简的调度.

如果在上述代码线程中加入 可能会触发GIL的操作 例如 puts 打印到屏幕:


@a = 1
r = []
10.times do |e|

Thread.new {
 @c = 1
 puts @c
 @c += @a
 r << [e, @c]
}
end
r
## [[2, 2], [0, 2], [4, 3], [5, 4], [7, 5], [9, 6], [1, 7], [3, 8], [6, 9], [8, 10]]

这个就会触发GIL的lock, 数据异常了.

小结

Web 应用大多是 IO 密集型的,利用 Ruby 多进程+多线程模型将能大幅提升系统吞吐量.其原因在于:当Ruby 某个线程处于 IO Block 状态时,其它的线程还可以继续执行,从而降低 IO Block 对整体的影响.但由于存在 Ruby GIL (Global Interpreter Lock),MRI Ruby 并不能真正利用多线程进行并行计算.

PS. 据说 JRuby 去除了GIL,是真正意义的多线程,既能应付 IO Block,也能充分利用多核 CPU 加快整体运算速度,有计划了解一些.

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung von Parallelität, Parallelität und globaler Sperrcode-Freigabe in Ruby. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn