Ruby-multithreading

提供:Dev Guides
移動先:案内検索

Ruby-マルチスレッド

従来のプログラムには単一の「実行のスレッド」があり、プログラムを構成するステートメントまたは命令は、プログラムが終了するまで順番に実行されます。

マルチスレッドプログラムには、複数の実行スレッドがあります。 各スレッド内では、ステートメントは順番に実行されますが、スレッド自体は、たとえばマルチコアCPU上で並列に実行されます。 多くの場合、単一のCPUマシンでは、複数のスレッドが実際に並列で実行されるわけではありませんが、スレッドの実行をインターリーブすることで並列性がシミュレートされます。

Rubyでは、_Thread_クラスを使用してマルチスレッドプログラムを簡単に作成できます。 Rubyスレッドは、コードの並行性を実現するための軽量で効率的な方法です。

Rubyスレッドの作成

新しいスレッドを開始するには、_Thread.new_への呼び出しにブロックを関連付けます。 ブロック内のコードを実行するために新しいスレッドが作成され、元のスレッドは_Thread.new_からすぐに戻り、次のステートメントで実行を再開します-

# Thread #1 is running here
Thread.new {
   # Thread #2 runs this code
}
# Thread #1 runs this code

次に、マルチスレッドRubyプログラムを使用する方法を示す例を示します。

#!/usr/bin/ruby

def func1
   i = 0
   while i<=2
      puts "func1 at: #{Time.now}"
      sleep(2)
      i = i&plus;1
   end
end

def func2
   j = 0
   while j<=2
      puts "func2 at: #{Time.now}"
      sleep(1)
      j = j&plus;1
   end
end

puts "Started At #{Time.now}"
t1 = Thread.new{func1()}
t2 = Thread.new{func2()}
t1.join
t2.join
puts "End at #{Time.now}"

これにより、次の結果が生成されます–

Started At Wed May 14 08:21:54 -0700 2008
func1 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:55 -0700 2008
func1 at: Wed May 14 08:21:56 -0700 2008
func2 at: Wed May 14 08:21:56 -0700 2008
func1 at: Wed May 14 08:21:58 -0700 2008
End at Wed May 14 08:22:00 -0700 2008

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

_Thread.new_で新しいスレッドが作成されます。 シノニム_Thread.start_および_Thread.fork_を使用することもできます。

スレッドを作成した後、スレッドを開始する必要はありません。CPUリソースが利用可能になると自動的に実行を開始します。

Threadクラスは、実行中のスレッドを照会および操作するためのいくつかのメソッドを定義します。 スレッドは、_Thread.new_の呼び出しに関連付けられたブロック内のコードを実行し、実行を停止します。

そのブロックの最後の式の値はスレッドの値であり、Threadオブジェクトの_value_メソッドを呼び出すことで取得できます。 スレッドが完了するまで実行された場合、値はスレッドの値をすぐに返します。 それ以外の場合、_value_メソッドはブロックされ、スレッドが完了するまで戻りません。

クラスメソッド_Thread.current_は、現在のスレッドを表すThreadオブジェクトを返します。 これにより、スレッドは自分自身を操作できます。 クラスメソッド_Thread.main_は、メインスレッドを表すThreadオブジェクトを返します。 これは、Rubyプログラムが開始されたときに開始された実行の初期スレッドです。

特定のスレッドが終了するのを待つには、そのスレッドの_Thread.join_メソッドを呼び出します。 呼び出しスレッドは、指定されたスレッドが終了するまでブロックします。

スレッドと例外

メインスレッドで例外が発生し、どこでも処理されない場合、Rubyインタープリターはメッセージを出力して終了します。 メインスレッド以外のスレッドでは、未処理の例外によりスレッドの実行が停止します。

未処理の例外のためにスレッド t が終了し、別のスレッド s が_t.joinまたはt.value、_を呼び出すと、 t で発生した例外がスレッド s で発生します。

_Thread.abort_on_exception_がデフォルトの状態である_false_である場合、未処理の例外は現在のスレッドを強制終了し、残りはすべて実行を継続します。

スレッドで未処理の例外を使用してインタープリターを終了するには、クラスメソッド_Thread.abort_on_exception_を_true_に設定します。

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

スレッド変数

スレッドは通常、スレッドの作成時にスコープ内にある変数にアクセスできます。 スレッドのブロックに対してローカルな変数はスレッドに対してローカルであり、共有されません。

スレッドクラスには、スレッドローカル変数を作成して名前でアクセスできる特別な機能があります。 スレッドオブジェクトをハッシュのように扱い、[] =を使用して要素に書き込み、[]を使用してそれらを読み返します。

この例では、各スレッドは、変数カウントの現在の値をキー_mycount_を持つthreadlocal変数に記録します。

#!/usr/bin/ruby

count = 0
arr = []

10.times do |i|
   arr[i] = Thread.new {
      sleep(rand(0)/10.0)
      Thread.current["mycount"] = count
      count &plus;= 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

メインスレッドは、サブスレッドが終了するのを待ってから、それぞれがキャプチャした_count_の値を出力します。

スレッドの優先度

スレッドのスケジューリングに影響を与える最初の要因は、スレッドの優先度です。優先度の高いスレッドは、優先度の低いスレッドよりも先にスケジュールされます。 より正確には、スレッドは、実行を待機している優先度の高いスレッドがない場合にのみCPU時間を取得します。

_priority = _および_priority_を使用して、Ruby Threadオブジェクトの優先度を設定および照会できます。 新しく作成されたスレッドは、それを作成したスレッドと同じ優先度で開始されます。 メインスレッドは優先度0で開始します。

実行を開始する前にスレッドの優先順位を設定する方法はありません。 ただし、スレッドは、最初のアクションとして自身の優先順位を上げたり下げたりできます。

スレッドの除外

2つのスレッドが同じデータへのアクセスを共有し、少なくとも1つのスレッドがそのデータを変更する場合、一貫性のない状態のデータがスレッドに表示されないように特に注意する必要があります。 これは_スレッド除外_と呼ばれます。

*Mutex* は、いくつかの共有リソースへの相互排他的アクセスのための単純なセマフォロックを実装するクラスです。 つまり、特定の時間にロックを保持できるスレッドは1つだけです。 他のスレッドは、ロックが使用可能になるのを待つか、ロックが使用不可であることを示す即時エラーを受け取ることを選択する場合があります。

共有データへのすべてのアクセスを_mutex_の制御下に置くことにより、一貫性とアトミック操作を保証します。 例を見てみましょう。最初はmutaxなし、2番目はmutaxあり-

Mutaxを使用しない例

#!/usr/bin/ruby
require 'thread'

count1 = count2 = 0
difference = 0
counter = Thread.new do
   loop do
      count1 &plus;= 1
      count2 &plus;= 1
   end
end
spy = Thread.new do
   loop do
      difference &plus;= (count1 - count2).abs
   end
end
sleep 1
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

これは、次の結果を生成します-

count1 :  1583766
count2 :  1583766
difference : 0
#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new

count1 = count2 = 0
difference = 0
counter = Thread.new do
   loop do
      mutex.synchronize do
         count1 &plus;= 1
         count2 &plus;= 1
      end
   end
end
spy = Thread.new do
   loop do
      mutex.synchronize do
         difference &plus;= (count1 - count2).abs
      end
   end
end
sleep 1
mutex.lock
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

これは、次の結果を生成します-

count1 :  696591
count2 :  696591
difference : 0

デッドロックの処理

スレッドの除外に_Mutex_オブジェクトの使用を開始するときは、_deadlock_を避けるように注意する必要があります。 デッドロックは、すべてのスレッドが別のスレッドによって保持されているリソースの取得を待機しているときに発生する状態です。 すべてのスレッドがブロックされているため、保持しているロックを解除できません。 また、ロックを解除できないため、他のスレッドはそれらのロックを取得できません。

これは、_条件変数_が登場する場所です。 _条件変数_は、リソースに関連付けられた特定の_mutex_の保護内で使用される単純なセマフォです。 使用できないリソースが必要な場合は、条件変数で待機します。 そのアクションは、対応する_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: Now I am critical, but am done with cv"
      cv.signal
      puts "B: I am still critical, finishing up"
   }
}
a.join
b.join

これは、次の結果を生成します-

A: I have critical section, but will wait for cv
(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!

スレッド状態

次の表に示すように、5つの可能な状態に対応する5つの可能な戻り値があります。 _status_メソッドは、スレッドの状態を返します。

Thread state Return value
Runnable run
Sleeping Sleeping
Aborting aborting
Terminated normally false
Terminated with exception nil

スレッドクラスメソッド

以下のメソッドは_Thread_クラスによって提供され、プログラムで利用可能なすべてのスレッドに適用できます。 これらのメソッドは、次のように_Thread_クラス名を使用して呼び出されます-

Thread.abort_on_exception = true

____これは、利用可能なすべてのクラスメソッドの完全なリストです-

Sr.No. Methods & Description
1

Thread.abort_on_exception

exception_条件でグローバル_abortのステータスを返します。 デフォルトは_false_です。 _true_に設定すると、スレッドで例外が発生した場合、すべてのスレッドが中止されます(プロセスはexit(0)になります)

2

Thread.abort_on_exception=

_true_に設定すると、例外が発生した場合、すべてのスレッドが中止されます。 新しい状態を返します。

3

Thread.critical

グローバル_thread critical_状態のステータスを返します。

4

Thread.critical=

グローバル_thread critical_状態のステータスを設定し、それを返します。 _true_に設定すると、既存のスレッドのスケジューリングが禁止されます。 新しいスレッドの作成と実行をブロックしません。 特定のスレッド操作(スレッドの停止または強制終了、現在のスレッドでのスリープ、例外の発生など)により、クリティカルセクションにある場合でもスレッドがスケジュールされることがあります。

5

Thread.current

現在実行中のスレッドを返します。

6

Thread.exit

現在実行中のスレッドを終了し、別のスレッドの実行をスケジュールします。 このスレッドがすでに終了するようにマークされている場合、_exit_は_Thread._を返します。これがメインスレッドまたは最後のスレッドである場合、プロセスを終了します。

7

Thread.fork \{ block }

Thread.newの同義語。

8

Thread.kill( aThread )

指定された_a Thread_を終了させます

9

Thread.list

実行可能または停止されているすべてのスレッドの_Thread_オブジェクトの配列を返します。 糸。

10

Thread.main

プロセスのメインスレッドを返します。

11 Thread.new( [ arg ] ) \{
args

block }*

ブロックで指定された命令を実行する新しいスレッドを作成し、実行を開始します。 _Thread.new_に渡された引数はすべてブロックに渡されます。

12

Thread.pass

スレッドスケジューラを呼び出して、実行を別のスレッドに渡します。

13 Thread.start( [ args ] ) \{
args

block }*

基本的に_Thread.new_と同じです。 ただし、クラス_Thread_がサブクラス化されている場合、そのサブクラスで_start_を呼び出しても、サブクラスの_initialize_メソッドは呼び出されません。

14

Thread.stop

現在のスレッドの実行を停止して_sleep_状態にし、別のスレッドの実行をスケジュールします。 _critical_条件をfalseにリセットします。

スレッドインスタンスメソッド

これらのメソッドは、スレッドのインスタンスに適用できます。 これらのメソッドは、次のように_Thread_のインスタンスを使用して呼び出されます-

#!/usr/bin/ruby

thr = Thread.new do   # Calling a class method new
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # Calling an instance method join

____これは、利用可能なすべてのインスタンスメソッドの完全なリストです-

Sr.No. Methods & Description
1

thr[ aSymbol ]

属性リファレンス-シンボルまたは_aSymbol_名のいずれかを使用して、スレッドローカル変数の値を返します。 指定された変数が存在しない場合、_nil_を返します。

2

thr[ aSymbol ] =

属性の割り当て-シンボルまたは文字列を使用して、スレッドローカル変数の値を設定または作成します。

3

thr.abort_on_exception

_thr_の_abort on exception_条件のステータスを返します。 デフォルトは_false_です。

4

thr.abort_on_exception=

_true_に設定すると、_thr_で例外が発生した場合、すべてのスレッド(メインプログラムを含む)が中止されます。 プロセスは事実上_exit(0)_になります。

5

thr.alive?

_thr_が実行中またはスリープ中の場合、_true_を返します。

6

thr.exit

_thr_を終了し、別のスレッドの実行をスケジュールします。 このスレッドがすでに終了するようにマークされている場合、_exit_は_Thread_を返します。 これがメインスレッドまたは最後のスレッドである場合、プロセスを終了します。

7

thr.join

呼び出しスレッドは実行を中断し、_thr_を実行します。 _thr_が終了するまで戻りません。 結合されていないスレッドは、メインプログラムが終了すると強制終了されます。

8

thr.key?

指定された文字列(またはシンボル)がスレッドローカル変数として存在する場合、_true_を返します。

9

thr.kill

_Thread.exit_の同義語。

10

thr.priority

_thr_の優先度を返します。 デフォルトはゼロです。優先度の高いスレッドは、優先度の低いスレッドよりも先に実行されます。

11

thr.priority=

_thr_の優先度を整数に設定します。 優先度の高いスレッドは、優先度の低いスレッドよりも先に実行されます。

12

thr.raise( anException )

_thr_から例外を発生させます。 呼び出し元は_thr_である必要はありません。

13

thr.run

_thr_をウェイクアップし、スケジューリングの対象にします。 クリティカルセクションにない場合は、スケジューラを呼び出します。

14

thr.safe_level

_thr_に対して有効な安全レベルを返します。

15

thr.status

thr_のステータスを返します。_thr_がスリープまたはI/Oで待機している場合は_sleep _、 thr_が実行中の場合は_run thr_が正常に終了した場合はfalse、thr_が例外で終了した場合は_nil

16

thr.stop?

_thr_がデッドまたはスリープ状態の場合、_true_を返します。

17

thr.value

thrが_Thread.join_を介して完了するまで待機し、その値を返します。

18

thr.wakeup

_thr_をスケジューリングの対象としてマークしますが、I/Oでブロックされたままになる場合があります。