Rubyの並列処理とグローバルロック
前置き
この記事は主にrubyの並列処理とグローバルロックに関する内容を紹介し、参考と学習のために共有しています。
並行処理と並列処理
開発現場では、「並行処理」と「並列処理」という2つの概念をよく目にしますが、並行処理と並列処理について書かれたもののほとんどに、あることが書かれています。では、この言葉をどう理解すればよいのでしょうか。
- 同時並行です。シェフが2人の顧客から同時に2つの注文を受け、それを処理する必要があります。 {を使用します。 順次実行。シェフが一人しかいない場合、次から次へとメニューをこなすしかない。
- 並列実行。料理人が2人いる場合、2人一緒に並行して調理することができます。
この例を私たちのWeb開発に拡張すると、次のように理解することができます。
- 並行処理:サーバーは2つのクライアントから同時にリクエストを受ける。 {を使用します。 順次実行:サーバーにはリクエストを処理するプロセス(スレッド)が1つしかなく、2番目のリクエストが完了する前に1番目のリクエストが完了するため、2番目のリクエストは待たなければならない。
- 並列実行。サーバーはリクエストを処理するために2つのプロセス(スレッド)を持ち、両方のリクエストは順次問題なく応答されます。
上記の例を踏まえて、このような並列動作をrubyでシミュレートするにはどうしたらよいでしょうか。次のコードを見てください。
1. 順次実行。
スレッドが1つしかない場合の動作をシミュレートします。
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
## user system total real
## sleep 3 seconds in f1
## sleep 2 seconds in f2
## 0.000000 0.000000 0.000000 ( 5.009620)
上のコードは、スリープを使って時間のかかる操作をシミュレートしているだけのシンプルなものです。シーケンシャル実行時に消費される時間。
2. 並列実行
マルチスレッド時の動作をシミュレート
# Pick up the above code
Benchmark.bm do |b|
b.report do
threads = []
threads << Thread.new { f1 }
threads << Thread.new { f2 }
threads.each(&:join)
end
end
## user system
## user system total real
## sleep 3 seconds in f1
## sleep 2 seconds in f2
## 0.000000 0.000000 0.000000 ( 3.005115)
複数のスレッドを使用した場合の所要時間は、f1の場合とほぼ同じであり、予想通り、複数のスレッドを使用して並列性を実現していることがわかります。 {これは予想通りで、複数のスレッドを使用することで並列性を実現できる。
{RubyのマルチスレッドはIOを処理できる。 RubyのマルチスレッドはIOブロックに対応できるため、あるスレッドがIOブロックに入った場合でも、他のスレッドが実行を継続することができ、全体の処理時間を大幅に短縮することができます。Rubyのスレッド
上記のコード例では、rubyのスレッドクラスThreadを使用していますが、RubyではThreadライクなマルチスレッドプログラムを簡単に記述することができます。 {Ruby の Thread は軽量で効率的に並列処理を実装する方法です。 /Ruby thread is a lightweight and efficient way to implement parallelism in your code.Rubyスレッドはあなたのコードに並列処理を実装するための軽量で効率的な方法です。
{次のセクションでは、並行処理のシナリオを説明します。 次に、並行処理のシナリオを説明します。 def thread_test
time = Time.now
threads = 3.times.map do
Thread.new do
sleep 3
end
end
puts "Don't have to wait 3 seconds to see me:#{Time.now - time}"
threads.map(&:join)
puts "Now I need to wait 3 seconds to see me:#{Time.now - time}"
end
test
## Don't have to wait 3 seconds to see me:8.6e-05
## Now you need to wait 3 seconds to see me:3.003699
Threadはノンブロッキングで生成されるため、テキストはすぐに出力されます。これは並行動作をシミュレートしています。各スレッドは3秒間スリープするので、ブロッキングの場合の並列動作が可能になる。
では、この時点で並列化は終了しているのでしょうか?
残念ながら、上記の説明では、ノンブロッキングの場合の並列性をシミュレートできることにしか触れていません。別の例を見てみましょう。
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)
ここから、同じタスクを4つのスレッドに分割して並行処理しても、時間が減らないことがわかりますが、なぜでしょうか? {なぜか?
グローバルロック(GIL)のせいだ !!!
グローバルロック
私たちが普段使っているrubyは、GILという仕組みを使っています。
複数のスレッドを使用してコードを並列化したくても、グローバルロックのため、一度に実行できるスレッドは1つだけで、どのスレッドが実行できるかは、基盤となるOSの実装に依存します。 {どのスレッドが実行できるかについては、基本的なOSの実装に依存します。
複数のCPUがあっても、各スレッドが実行できるオプションが少し増えるだけなのです。
上記のコードでは、一度に1つのスレッドだけが count += 1 を実行することができます。
Rubyのマルチスレッドは複数のCPUを再利用するわけではないので、マルチスレッドに費やす全体の時間は減りませんが、スレッド切り替えの影響により若干増えることがあります。
でも、寝ているときは明らかに並列化しているんですよ!?
これはRubyの先進的な設計で、ファイルの読み書きやネットワークリクエストなど、すべてのブロック操作が並列化できるんだ。
{{コード
ネットワークリクエスト中はプログラムがブロックされますが、このブロックはRubyの下で並列化されているので、かかる時間は大幅に短縮されます。
GILについて考える
では、このGILロックがあれば、私たちのコードはスレッドセーフということになるのでしょうか?
残念ながらそうではありません。GILはrubyの実行中のある時点で別のスレッドに切り替わるので、いくつかのクラス変数が共有されている場合は落とし穴になる可能性があります。
では、GILはどのような場合にrubyコード実行中に他のスレッドに切り替わるのでしょうか?
明確な作業ポイントがいくつかあります。
- {を使用します。
メソッド呼び出しとメソッド戻りでは、現在のスレッドのギルに対するロックがタイムアウトしたかどうか、他のスレッドにディスパッチすべきかどうかがチェックされます。
{を使用します。
すべてのio関連の操作について、他のスレッドが作業できるようにgilロックも解放される
{を使用します。
c 拡張コードで gil のロックを手動で解放する。
- もう一つわかりにくいのは、rubyのスタックもcのスタックに入るとgil検出のトリガーになることです
一例
require 'benchmark'
require 'net/http'
# Simulate network requests
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)
上記のrでは、eの順序は異なるが、@cの値は常に2、すなわち各スレッドで@cの現在値が保存されている。スレッドのスケジューリングがない。
{スレッドのスケジューリングはない。
上記のコードスレッドに追加すると、putsなどの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]]
これは、GILロック、データ例外をトリガーします。
概要
WebアプリケーションはIOを多用するため、Rubyのマルチプロセシング+マルチスレッドモデルを使用すると、システムのスループットが大幅に向上します。これは、Ruby のスレッドが IO Block になっても、他のスレッドが実行を継続できるため、IO Block の影響を全体的に軽減することができるからです。しかし、RubyのGIL(Global Interpreter Lock)のため、MRI Rubyでは複数スレッドによる並列計算をあまり活用できていません。
{PS. 追記 JRubyはGILを使わない真のマルチスレッド化により、IO Blockに対応し、マルチコアCPUを駆使して全体の計算を高速化できると言われており、今後さらに勉強する予定があります。概要
上記はこの記事のすべての内容です、私はあなたの勉強や仕事のためのこの記事の内容は、特定の参照学習価値があることを願って、あなたが交換するメッセージを残すことができる質問がある場合は、BinaryDevelopをサポートしていただき、ありがとうございます。
関連
-
最新のCocoaPodsインストールチュートリアル
-
バブルソートアルゴリズムの簡易実装とRuby版
-
Rubyのデザインパターンプログラミングにおけるコマンドパターンの活用を徹底分析
-
CentOS7でruby on railsの開発環境を構築する。
-
rubyのダブルイコール==問題
-
RubyGnome2 ライブラリを用いた GTK 環境での Ruby GUI プログラミングの基本的な考え方
-
Rubyメタプログラミングの注目すべき点
-
Rubyのコードコメントを書く際に気をつけるべき事項
-
Ruby on RailsでMarkdownを使用する方法
-
Rubyのクラスとモジュールの書き方のスタイルガイド
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
redisクラスタ構築のチュートリアルと発生した問題
-
Ruby Hash ハッシュ型 基本操作のメソッド一覧 まとめ
-
Rubyのgemパッケージ管理およびgemソース構築のチュートリアル
-
Ruby on Railsで構築するアプリケーションの基本的なディレクトリ構造のまとめ
-
Ruby は REXML ライブラリを使って xml 形式のデータをパースする
-
RubyがWeb画像クローリングを実装
-
Rubyの4つの比較関数(equal?, eql?, ==, ===)について解説します。
-
Ruby on Railsのjquery_ujsコンポーネントが遅くなる問題が解決された
-
Ruby on Railsのビューの書き方に関するいくつかのアドバイス
-
Rubyで配列とハッシュテーブルを使う