Edited at

【Ruby】Thread(スレッド)を理解する


Thread(スレッド)とは?

Thread(スレッド)とはプログラムの一連の処理のまとまりのことです。

複数の処理の流れ(スレッド)を持つプログラムをマルチスレッドのプログラムと呼び、複数の処理を並行して実行させるプログラミングのことを並行(concurrent)プログラミングと呼びます。

RubyのThreadクラスはこの並行(concurrent)プログラミングを実現するために使用されます。


Rubyでマルチスレッドを作成する。

プログラム開始時に生成されるスレッドはメインスレッド、現在実行中のスレッドはカレントスレッドと呼ばれます。

RubyではThread#mainを用いる事でメインスレッドを確認できます。

また、Thread#listでプログラム上に存在するスレッドが配列で表示されます。

p Thread.main #=> #<Thread:0x00007f9600882f00 run> #プログラム開始時に作成されるメインスレッド

p Thread.list #=> [#<Thread:0x00007f9600882f00 run>] #スレッドは1つのみ。
p "Hello World" #=> "Hello World"

上記の例ではプログラミング開始時に作成されたメインスレッド1つのみが存在している事が確認できます。

それでは、実際にRubyでマルチスレッドのプログラムを作成してみましょう。

RubyではThread#new/Thread.fork/Thread.startのいずれかでスレッドを作成可能です。

p Thread.main #=> #<Thread:0x00007f9d5d082ee0 run> #メインスレッド

Thread.new { 'Hello World' } #別のスレッドの作成
Thread.fork { 'Good Morning World' } #別のスレッドの作成②

#メインスレッドに加えて上で作成した2つのスレッドが配列に格納されている。
p Thread.list #=> [#<Thread:0x00007f9d5d082ee0 run>, #<Thread:0x00007f9d5d113580@code11.rb:2 run>, #<Thread:0x00007f9d5d1133a0@code11.rb:3 dead>]

メインスレッドに加えて作成した他2つのスレッドが配列に格納されていることが確認できます。

Rubyでマルチスレッドを生成することが出来ました。


Rubyで並行プログラミングの実行

それでは、スレッドを使用することで「何が出来るのか」、「並行プログラミングとは何か」をサンプルコードを交えて説明します。

以下のコードを用いて、スレッドを使用しない場合とした場合の出力結果の違いを確認してみます。

def time1

sleep(2)
puts Time.now.strftime("%H時%M分%S秒")
end

def time2
sleep(2)
puts Time.now.strftime("%H時%M分%S秒")
end

スレッドを使用しない場合は通常通りプログラムは上から実行され、time1time2の出力する時間にはKernel#sleepで待機させた2秒の時間差が生まれます。

def time1

sleep(2)
puts Time.now.strftime("%H時%M分%S秒")
end

def time2
sleep(2)
puts Time.now.strftime("%H時%M分%S秒")
end
#何も指示がない場合、プログラムは上から下へと実行されていく。
time1 #=> 13時23分30秒
time2 #=> 13時23分32秒

以下がスレッドを使用して実行した結果です。

Thread#joinを用いる事でメインスレッドの実行を中断し、指定されたスレッドを実行、終了することができます。

def time1

sleep(2)
puts Time.now.strftime("%H時%M分%S秒")
end

def time2
sleep(2)
puts Time.now.strftime("%H時%M分%S秒")
end
#スレッドを仕様しない場合
time1 #=> 13時23分30秒
time2 #=> 13時23分32秒

## Threadを利用した例
threads = []
threads << Thread.new { time1() }
threads << Thread.fork { time2() }
threads.each { |thr| thr.join }
#=> 13時23分34秒
#=> 13時23分34秒

スレッドを使用した場合は上のように複数の処理を並行に実行することが可能です。

これによってtime1とtime2の出力結果が同じものになりました。


Thread(スレッド)の生成

上記でも記載しましたが、以下のサンプルコード上の方法でスレッドを生成することが可能です。

スレッドを生成するメソッドの後にブロックを伴わない場合には作成できずThreadErrorを引き起こすので注意が必要です。

また、Thread.newThread.start/Thread.forkは微妙に意味が異なります。詳しくは以下のリンク先を参考にしてください。

Rubyリファレンス

Thread.new {} #=> #<Thread:0x00007fdf4c154e00@code11.rb:1 run>

Thread.start {} #=> #<Thread:0x00007fdf4c154ab8@code11.rb:2 run>
Thread.fork {} #=> #<Thread:0x00007fdf4c154860@code11.rb:3 run>
thr = Thread.new #=> `initialize': must be called with a block (ThreadError)


Thread(スレッド)のライフサイクル

Thread(スレッド)には以下のような5つの状態があります。

あるスレッドの状態はライフサイクルの中で変化していきます。

<Thread:0x00007fdf4c154860@code11.rb:3 run>の末尾にあるrunがスレッドの状態を表しています。

状態
意味

run
実行可能、または実行中(生成直後やThread#runThread#wakeupで起こされたスレッドはこの状態になる。)

sleep
停止中(Thread.stopThread#joinにより一時停止されたスレッドはこの状態になる。)

aborting
終了処理中(Thread#killなどで終了されるスレッドは一時的にこの状態になる。)

false
正常に終了した(Thread#killで終了したり、正常終了したスレッドの場合falseが返る。)

nil
例外などで異常終了した場合にはnilが返る。

以下のメソッドでスレッドの状態を確認することができます。

状態
意味

status
状態を文字列で返す。

alive?
スレッドが生きているかどうかをtrueかfalseで返す。

stop?
スレッドが停止しているかどうかをtrueかfalseで返す。


Thread(スレッド)の操作

以下のようなメソッドでスレッドの状態の操作が可能です。

状態
意味

wakeup
停止スレッドを実行可能状態(run)にする。

run
即座に処理をそのスレッドに切り替える。

kill/terminate/exit
生きている状態のスレッドの終了/既に終了しているスレッドに対しては何も行わない。

stop
実行したスレッド自身を停止状態にする。

pass
現在のスレッドから他のスレッドに実行を移す


参考

Ruby-Doc(Thread)

Rubyリファレンス(Thread)