Heim > Artikel > Backend-Entwicklung > Detaillierte Erläuterung von Parallelität, Parallelität und globaler Sperrcode-Freigabe in Ruby
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
# 接上述代码 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
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.003699Haben 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:
require 'benchmark' 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
require 'benchmark' require 'net/http' # 模拟网络请求 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!