Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

並行性の詳細:マルチスレッド

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の外部で動作します。つまり、この場合、実際に適切な同時実行性を実現できます。

おわりに

これで、接続ごとにスレッドを使用して、単一のプロセス内でチャットサーバーが実行されます。これにより、マルチプロセス実装よりもはるかに少ないリソースが使用されます。コードの詳細を確認したり、試してみたい場合は、ここにサンプルコードがあります。

このシリーズの最後の記事では、シングルスレッドとイベントループを使用して、これと同じチャットサーバーを実装します。理論的には、これはスレッドの実装よりも少ないリソースを使用するはずです!


  1. Railsの詳細を確認するタイミング

    自分にとって意味のないRailsトピックを見つけたことがありますか? たとえば、あなたはそれを知っていると思ったので、コードを書いたのですが、まったく別のことが起こりましたか? または、知っている あなたは理解していませんが、エッジケースとの戦いに多くの時間を費やしているので、それが終わったときには専門家であった可能性があることを除けば、十分に理解していますか? ええと、そうする必要がなかったらどうしますか? 知っているだけの方がいいのではないでしょうか 物事はどのように機能しましたか?あなたの目の前に問題のしっかりしたメンタルモデルを持っているために? では、適切な決定を下し、適切なコ

  2. Mario Peshev による 50 人以上の WordPress スタジオの構築の詳細

    起業家精神とは、ほとんどの人ができないように残りの人生を過ごすことができるように、ほとんどの人がそうしないようにあなたの人生の数年間を生きることです. 」 MalCare では、さまざまな方法で WordPress コミュニティに貢献することに注力しました。私たちは、Web セキュリティについてさらに学びたい WordPress ユーザーにとって貴重な情報源となるよう努めています。ただし、少しズームアウトして、WordPress コミュニティ全般に関連するトピックについて話したい場合もあります. 今日、まさにそれを行う機会がありました。最近、チャットする機会がありました それは私の