English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Ruby マルチスレッド

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。

线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。

Ruby 中我们可以通过 Thread 类来创建多线程,Ruby的线程是一个轻量级的,可以以高效的方式来实现并行的代码。

创建 Ruby 线程

要启动一个新的线程,只需要调用 Thread.new 即可:

# 线程 #1 代码部分
Thread.new {
  # 线程 #2 执行代码
}
# 线程 #1 执行代码

オンラインサンプル

以下示例展示了如何在Ruby程序中使用多线程:

オンラインサンプル

#!/usr/bin/ruby
 
def func1
   i=0
   while i<=2
      puts "func1 at: #{Time.now}"
      sleep(2)
      i=i+1
   end
end
 
def func2
   j=0
   while j<=2
      puts "func2 at: #{Time.now}"
      sleep(1)
      j=j+1
   end
end
 
puts "Started At #{Time.now}"
t1=Thread.new{func1()}
t2=Thread.new{func2()}
t1.join
t2.join
()}

上記のコードの実行結果は以下の通りです:

t 14 08:21:54 -0700 2014
puts "End at #{Time.now}"1 Started At Wed May 14 08:21:54 -0700 2014
puts "End at #{Time.now}"2 Started At Wed May 14 08:21:54 -0700 2014
puts "End at #{Time.now}"2 Started At Wed May 14 08:21:55 -0700 2014
puts "End at #{Time.now}"1 Started At Wed May 14 08:21:56 -0700 2014
puts "End at #{Time.now}"2 Started At Wed May 14 08:21:56 -0700 2014
puts "End at #{Time.now}"1 Started At Wed May 14 08:21:58 -0700 2014
func 14 08:22at: Wed May -0700 2014

End at Wed May

1:00

2スレッドのライフサイクル

3、スレッドの作成後は自動的に実行されます。スレッドの作成にはThread.newを使用しますが、Thread.startまたはThread.forkを使用することもできます。

4、Threadクラスはスレッドを操作するためのメソッドをいくつか定義しています。スレッドはThread.newのブロックを実行します。

5、スレッドコードブロックの最後の行はスレッドの値であり、スレッドのメソッドを使用して呼び出すことができます。スレッドが実行完了した場合、スレッドの値が返されますが、実行中の場合は値が返されません。

6、Thread.currentメソッドは現在のスレッドを表すオブジェクトを返します。Thread.mainメソッドは主スレッドを返します。

スレッドの状態

スレッドを実行するにはThread.Joinメソッドを使用します。このメソッドは、現在のスレッドを待機状態にし、現在のスレッドが終了するまで待機します。5以下の

スレッドの状態返り値
実行可能run
睡眠Sleeping
退出aborting
正常終了false
例外終了nil

スレッドと例外

スレッドが例外を発生させ、rescueでキャッチされていない場合、そのスレッドは通常無警告で終了します。しかし、他のスレッドがThread#joinの関係でそのスレッドを待っている場合、待っているスレッドも同じ例外が発生します。

begin
  t = Thread.new do
    Thread.pass    # 主スレッドがjoinを待っている場合
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

以下の3この方法を使用すると、特定のスレッドが例外を発生させた場合にインタープリタが中断されます。

  • スクリプトの起動時に指定-dオプションを使用して、デバッグモードで実行します。

  • Thread.abort_on_exceptionを使用してフラグを設定します。

  • Thread#abort_on_exceptionを使用して指定されたスレッドにフラグを設定します。

上記の3その方法の1つを使用した後、全体のインタープリタが中断されます。

t = Thread.new { ... }
t.abort_on_exception = true

スレッド同期制御

Rubyでは、同期を実現するための3つの方法が提供されています:

1Mutexクラスを通じてスレッド同期を実現します。

2. データの移行を監督するQueueクラスを使用してスレッド同期を実現

3. ConditionVariableを使用して同期制御を実現

Mutex クラスを使用してスレッド同期を実現

Mutex クラスを使用してスレッド同期制御を実現します。複数のスレッドが同じプログラム変数が必要な場合、その変数の一部をロックする方法があります。 以下はコードです:

オンラインサンプル

#!/usr/bin/ruby
 
require "thread"
puts "同期スレッド"
 
@num=200
@mutex=Mutex.new
 
def buyTicket(num)
     @mutex.lock
          if @num>=num
               @num=@num-num
               puts "あなたは成功して#{num}チケットを購入しました"
          else
               puts "ごめんなさい、チケットが十分ではありません"
          end
     @mutex.unlock
end
 
チケット1=Thread.new 10 do
     10.times do |value|
     ticketNum=15
     buyTicket(ticketNum)
     sleep 0.01
     end
end
 
チケット2=Thread.new 10 do
     10.times do |value|
     ticketNum=20
     buyTicket(ticketNum)
     sleep 0.01
     end
end
 
).abs 1
チケット1.join
チケット2.join

上記のコードの実行結果は以下の通りです:

同期スレッド
あなたは成功してチケットを購入しました 15 チケット
あなたは成功してチケットを購入しました 20 チケット
あなたは成功してチケットを購入しました 15 チケット
あなたは成功してチケットを購入しました 20 チケット
あなたは成功してチケットを購入しました 15 チケット
あなたは成功してチケットを購入しました 20 チケット
あなたは成功してチケットを購入しました 15 チケット
あなたは成功してチケットを購入しました 20 チケット
あなたは成功してチケットを購入しました 15 チケット
あなたは成功してチケットを購入しました 20 チケット
あなたは成功してチケットを購入しました 15 チケット
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません
ごめんなさい、チケットが十分ではありません

lock 变数を使用するだけでなく、try_lock 变数を使用して変数をロックし、Mutex.synchronize を使用して特定の変数へのアクセスを同期できます。

データの移行を監督する Queue クラスはスレッド同期を実現

Queue クラスはスレッドをサポートするクラスを表し、クエーueの末尾へのアクセスを同期できます。異なるスレッドは同じクラスを使用できますが、このクエーue内のデータが同期できるかどうかを心配しないでください。また、SizedQueue クラスを使用すると、クエーueの長さを制限できます。

SizedQueue クラスは非常に便利に私たちがスレッド同期のアプリケーションを開発するのに役立ちます。なぜなら、このクラスに追加すると、スレッドの同期問題を心配しなくても良いからです。

经典のプロデューサー・コンサumber problem:

オンラインサンプル

#!/usr/bin/ruby
 
require "thread"
puts "SizedQuee テスト"
 
queue = Queue.new
 
producer = Thread.new do
     10.times do |i|
          sleep rand(i) # 线程睡眠一段时间
          queue << i
          puts "#{i} 生成"
     end
end
 
consumer = Thread.new do
     10.times do |i|
          value = queue.pop
          sleep rand(i/2)
          puts "consumed #{value}"
     end
end
 
consumer.join

プログラムの出力:

SizedQuee Test
0 produced
1 produced
consumed 0
2 produced
consumed 1
consumed 2
3 produced
consumed 34 produced
consumed 4
5 produced
consumed 5
6 produced
consumed 6
7 produced
consumed 7
8 produced
9 produced
consumed 8
consumed 9

スレッド変数

スレッドにはプライベートな変数があります。スレッドのプライベートな変数はスレッドが作成されたときにスレッドに書き込まれ、スレッドの範囲内で使用できますが、スレッド外部では共有することはできません。

しかし、時にはスレッドのローカル変数が他のスレッドやメインスレッドにアクセスする必要がある場合があります。Ruby はスレッド変数を名前で作成する機能を提供しており、スレッドをハッシュテーブルのように扱うことができます。データを []= で書き込み、[] で読み出すことができます。以下のコードを見てみましょう:

オンラインサンプル

#!/usr/bin/ruby
 
count = 0
arr = []
 
10.times do |i|
   arr[i] = Thread.new {
      sleep(rand(0)/10.0)
      Thread.current["mycount"] = count
      count +counter = Thread.new do 1
   }
end
 
arr.each {|t| t.join; print t["mycount"], ", "}
puts "count = #{count}"

上記のコードの実行結果は以下の通りです:

8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10

メインスレッドはサブスレッドの実行を待ち、それぞれの値を出力します。 。

スレッド優先度

スレッドの優先度はスレッドのスケジューリングに影響を与える主要な要因です。他の要因には、CPU 使用時間の長さ、スレッドのグループスケジューリングなどが含まれます。

スレッドの優先度を取得するには Thread.priority メソッドを使用し、優先度を変更するには Thread.priority = メソッドを使用します。

スレッドの優先度はデフォルトで 0 です。優先度が高いスレッドは早く実行されます。

一つの Thread は自身の範囲内のすべてのデータにアクセスできますが、あるスレッド内で他のスレッドのデータにアクセスする必要がある場合どうすればいいですか? Thread クラスはスレッドデータの相互アクセス方法を提供しており、スレッドをハッシュテーブルとして簡単に扱うことができます。どのスレッドでもデータを []= で書き込み、[] で読み出すことができます。

athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop }
bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop }
cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop }
Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}" }

スレッドをハッシュテーブルとして扱い、[]と[]=メソッドを使用して、スレッド間のデータ共有を実現しています。

スレッドの排他

Mutex(Mutal Exclusion = 互斥锁)は、多线程プログラミングで、2つのスレッドが同時に同じ公共リソース(例えば、グローバル変数)に対して読み書きを行わないようにするメカニズムです。

不使用Mutax的示例

オンラインサンプル

#!/usr/bin/ruby
require 'thread'
 
count1 count2 =
=
difference = 0
   spy = Thread.new do
      count1 +counter = Thread.new do 1
      count2 +counter = Thread.new do 1
   end
end
=
   spy = Thread.new do
      mutex.synchronize do +difference1 - count2= (count
   end
end
).abs 1
mutex.lock1 puts "count1":  #{count
mutex.lock2 puts "count2":  #{count
puts "difference : #{difference}"

以上示例运行输出结果为:

count1 :  9712487
count2 :  12501239
difference : 0

使用 mutex 的示例

オンラインサンプル

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
count1 count2 =
=
difference = 0
   spy = Thread.new do
      loop do
         count1 +counter = Thread.new do 1
         count2 +counter = Thread.new do 1
      end
    end
end
=
   spy = Thread.new do
       loop do
          mutex.synchronize do +difference1 - count2= (count
       end
   end
end
).abs 1
sleep
mutex.lock1 puts "count1":  #{count
mutex.lock2 puts "count2":  #{count
puts "difference : #{difference}"

以上示例运行输出结果为:

count1 :  1336406
count2 :  1336406
difference : 0

死锁

两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,这种状况,就称为死锁。

例如,一个进程 p1占用了显示器,同时又必须使用打印机,而打印机被进程p2占用,p2又必须使用显示器,这样就形成了死锁。

当我们在使用 Mutex 对象时需要注意线程死锁。

オンラインサンプル

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: I have critical section, but will wait for cv"
      cv.wait(mutex)
      puts "A: I have critical section again! I rule!"
   }
}
 
puts "(Later, back at the ranch...)"
 
b = Thread.new {
   mutex.synchronize {
      puts "B: さて、私はクリアセクション中ですが、cvで終わりました"
      cv.signal
      puts "B: まだクリアセクション中、最後までやります"
   }
}
a.join
b.join

上記の例の出力結果は以下の通りです:

A: クリアセクションがありますが、cvを待ちます
(その後、再び牧場に戻って...)
B: さて、私はクリアセクション中ですが、cvで終わりました
B: まだクリアセクション中、最後までやります
A: もう一度クリアセクションがあります!私は王様です!

スレッドクラスメソッド

Thread(スレッド)クラスのメソッドは以下の通りです:

番号メソッドの説明
1Thread.abort_on_exception
この値がtrueの場合、あるスレッドが例外により終了すると、全体のインタープリタが中断されます。デフォルト値はfalseであり、通常、スレッドが例外を発生させた場合でも、その例外がThread#joinなどの検出に未検出であれば、そのスレッドは警告無しで終了されます。
2Thread.abort_on_exception=
設定するときは、 trueあるスレッドが例外により終了すると、全体のインタープリタが中断されます。新しい状態を返します。
3Thread.critical
ボルン値を返します。
4Thread.critical=
この値がtrueの場合、スレッド切換えは行われません。現在のスレッドが停止状態(stop)またはシグナル(signal)によって干渉された場合、この値は自動的にfalseに変更されます。
5Thread.current
現在実行中のスレッド(現在のスレッド)を返します。
6Thread.exit
現在のスレッドの実行を終了し、現在のスレッドを返します。現在のスレッドが唯一のスレッドの場合、exit(0)を使用して実行を終了します。
7Thread.fork { block }
Thread.newと同様にスレッドを生成します。
8Thread.kill(aThread)
スレッドの実行を終了します。
9Thread.list
実行中または停止状態のアクティブスレッドの配列を返します。
10Thread.main
メインスレッドに戻ります。
11Thread.new([arg])* ) {| args | block }
スレッドを生成し、実行を開始します。引数はそのままブロックに渡されます。これにより、スレッドを起動する際に値をスレッド固有のローカル変数に渡すことができます。
12Thread.pass
他线程に実行権を渡します。現在実行中のスレッドの状態は変更されず、他の実行可能なスレッドに制御権を渡します(明示的なスレッドスケジューリング)。
13Thread.start( [ args ])* ) {| args | block }
スレッドを生成し、実行を開始します。引数はそのままブロックに渡されます。これにより、スレッドを起動する際に値をスレッド固有のローカル変数に渡すことができます。
14Thread.stop
現在のスレッドを停止し、他のスレッドがrunメソッドを使用してスレッドを再び唤醒するまで待機します。

スレッドメソッド

以下のサンプルでは、スレッドメソッドjoinが呼び出されています:

オンラインサンプル

#!/usr/bin/ruby
 
thr = Thread.new do   # メソッドの呼び出し例
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # メソッドの呼び出し例

以下に完全なメソッドリストを示します:

番号メソッドの説明
1thr[ name ]
スレッド内でnameに対応するスレッド固有データを取得します。nameは文字列またはシンボルです。 nameに対応するデータがない場合、nilを返します。
2thr[ name ]=
スレッド内でnameに対応するスレッド固有データの値を設定します。nameは文字列またはシンボルです。nilに設定すると、スレッド内の対応データが削除されます。
3thr.abort_on_exception
ボルン値を返します。
4thr.abort_on_exception=
その値がtrueの場合、某スレッドが例外で終了するとき、全体のインタプリタが中断されます。
5thr.alive?
スレッドが「生きている」場合、trueを返します。
6thr.exit
スレッドの運行を終了します。selfを返します。
7thr.join
現在のスレッドを停止し、selfスレッドが終了運行まで待機します。selfが例外で終了するとき、現在のスレッドも同じ例外を発生させます。
8thr.key?
nameに対応するスレッド固有データが既に定義されている場合、trueを返します。
9thr.kill
似ている Thread.exit
10thr.priority
スレッドの優先度を返します。優先度のデフォルト値は0です。この値が大きいほど優先度が高いです。
11thr.priority=
スレッドの優先度を設定します。負の数にも設定できます。
12thr.raise( anException )
そのスレッド内で強制的に例外を発生させます。
13thr.run
停止された(stop)スレッドを再起動します。wakeupとは異なり、スレッドの切り替えが即座に行われます。死プロセスに対してこのメソッドを使用すると、ThreadError例外が発生します。
14thr.safe_level
self のセキュリティレベルを返します。現在のスレッドの safe_level は $SAFE と同じです。
15thr.status
活スレッドの状態を「run」、「sleep」または「aborting」の文字列で表します。スレッドが正常に終了した場合、false を返します。例外で終了した場合、nil を返します。
16thr.stop?
スレッドが終了状態(dead)または停止状態(stop)にある場合、true を返します。
17thr.value
self スレッドが終了するまで待ち、そのスレッドのブロックの返値を返します。スレッドの実行中に例外が発生した場合、その例外が再び発生します。
18thr.wakeup
停止状態(stop)のスレッドの状態を実行可能状態(run)に変更します。このメソッドを死スレッドに適用すると、ThreadError 例外が発生します。