並行性の詳細:マルチスレッド
Ruby Magicの前の版では、複数のプロセスを使用してチャットシステムを実装する方法を示しました。今回は、複数のスレッドを使用して同じことを行う方法を紹介します。
簡単な要約
基本的なセットアップの完全な説明が必要な場合は、前の記事を確認してください。ただし、すぐに思い出してください。チャットシステムは次のようになります。
以前に使用したのと同じクライアントを使用しています:
# client.rb
# $ ruby client.rb
require 'socket'
client = TCPSocket.open(ARGV[0], 2000)
Thread.new do
while line = client.gets
puts line.chop
end
end
while input = STDIN.gets.chomp
client.puts input
end
サーバーの基本的な設定は同じです:
# server_threads.rb
# $ ruby server_threads.rb
require 'socket'
puts 'Starting server on port 2000'
server = TCPServer.open(2000)
この記事の例で使用されている完全なソースコードはGitHubで入手できるため、自分で試すことができます。
マルチスレッドチャットサーバー
ここで、マルチプロセスの実装とは異なる部分に到達します。 マルチスレッドの使用 1つのRubyプロセスで同時に複数のことを実行できます。これを行うには、作業を行う複数のスレッドを生成します。
スレッド
スレッドは独立して実行され、プロセス内でコードを実行します。複数のスレッドが同じプロセスに存在し、メモリを共有できます。
<img src="/images/blog/2017-04/threads.png">
着信チャットメッセージを保存するには、ある程度のストレージが必要になります。プレーンなArray
を使用します 、ただし、Mutex
も必要です 1つのスレッドだけが同時にメッセージを変更することを確認します(Mutex
がどのように変更されるかを確認します) 少し動作します)。
mutex = Mutex.new
messages = []
次に、チャットクライアントからの着信接続を受け入れるループを開始します。接続が確立されると、そのクライアント接続からの着信メッセージと発信メッセージを処理するためのスレッドが生成されます。
Thread.new
server.accept
までブロックを呼び出します 何かを返し、新しく作成されたスレッドで次のブロックを生成します。次に、スレッド内のコードは、送信された最初の行の読み取りに進み、これをニックネームとして保存します。最後に、メッセージの送信と読み取りを開始します。
loop do
Thread.new(server.accept) do |socket|
nickname = read_line_from(socket)
# Send incoming message (coming up)
# Read incoming messages (coming up)
end
end
ミュートス
ミューテックスは、複数のスレッドが配列などの共有リソースをどのように使用するかを調整できるようにするオブジェクトです。スレッドは、アクセスが必要であることを示すことができます。この間、他のスレッドは共有リソースにアクセスできません。
サーバーは、ソケットから着信メッセージを読み取ります。 synchronize
を使用します メッセージストアをロックして、メッセージをメッセージArray
に安全に追加できるようにします。 。
# Read incoming messages
while incoming = read_line_from(socket)
mutex.synchronize do
messages.push(
:time => Time.now,
:nickname => nickname,
:text => incoming
)
end
end
最後に、Thread
サーバーによって受信されたすべての新しいメッセージがクライアントに送信されていることを確認するために、ループで継続的に実行されるが生成されます。ここでもロックが取得されるため、他のスレッドが干渉していないことがわかります。ループのティックが完了すると、少しスリープしてから続行します。
# Send incoming message
Thread.new do
sent_until = Time.now
loop do
messages_to_send = mutex.synchronize do
get_messages_to_send(nickname, messages, sent_until).tap do
sent_until = Time.now
end
end
messages_to_send.each do |message|
socket.puts "#{message[:nickname]}: #{message[:text]}"
end
sleep 0.2
end
end
グローバルインタプリタロック
Rubyのグローバルインタープリターロック(GIL)が原因で、Rubyが「実際の」スレッド化を実行できないという話を聞いたことがあるかもしれません。これは部分的に真実です。 GILは、すべてのRubyコードの実行をロックし、Rubyプロセスが複数のCPUを同時に使用するのを防ぎます。 IO操作(この記事で使用したネットワーク接続など)はGILの外部で動作します。つまり、この場合、実際に適切な同時実行性を実現できます。
おわりに
これで、接続ごとにスレッドを使用して、単一のプロセス内でチャットサーバーが実行されます。これにより、マルチプロセス実装よりもはるかに少ないリソースが使用されます。コードの詳細を確認したり、試してみたい場合は、ここにサンプルコードがあります。
このシリーズの最後の記事では、シングルスレッドとイベントループを使用して、これと同じチャットサーバーを実装します。理論的には、これはスレッドの実装よりも少ないリソースを使用するはずです!
-
Railsの詳細を確認するタイミング
自分にとって意味のないRailsトピックを見つけたことがありますか? たとえば、あなたはそれを知っていると思ったので、コードを書いたのですが、まったく別のことが起こりましたか? または、知っている あなたは理解していませんが、エッジケースとの戦いに多くの時間を費やしているので、それが終わったときには専門家であった可能性があることを除けば、十分に理解していますか? ええと、そうする必要がなかったらどうしますか? 知っているだけの方がいいのではないでしょうか 物事はどのように機能しましたか?あなたの目の前に問題のしっかりしたメンタルモデルを持っているために? では、適切な決定を下し、適切なコ
-
Mario Peshev による 50 人以上の WordPress スタジオの構築の詳細
起業家精神とは、ほとんどの人ができないように残りの人生を過ごすことができるように、ほとんどの人がそうしないようにあなたの人生の数年間を生きることです. 」 MalCare では、さまざまな方法で WordPress コミュニティに貢献することに注力しました。私たちは、Web セキュリティについてさらに学びたい WordPress ユーザーにとって貴重な情報源となるよう努めています。ただし、少しズームアウトして、WordPress コミュニティ全般に関連するトピックについて話したい場合もあります. 今日、まさにそれを行う機会がありました。最近、チャットする機会がありました それは私の