やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

組込ライブラリ(Thread)

 スレッド。

成果物

情報源

Thread

スレッドを表すクラスです。スレッドとはメモリ空間を共有して同時に実行される制御の流れです。 Thread を使うことで並行プログラミングが可能になります。

実装

ネイティブスレッドを用いて実装されていますが、現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行されるネイティブスレッドは常にひとつです。ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。また拡張ライブラリから GVL を操作できるので、複数のスレッドを同時に実行するような拡張ライブラリは作成可能です。

 基本的には事実上ひとつだけ。それでマルチコアを活かすことができるのか?

スケジューリング

Ruby のスレッドスケジューリングはネイティブスレッドのそれを利用しています。よって詳細はプラットフォームに依存します。

メインスレッド

プログラムの開始と同時に生成されるスレッドを「メインスレッド」と呼びます。なんらかの理由でメインスレッドが終了する時には、他の全てのスレッドもプログラム全体も終了します。ユーザからの割込みによって発生した例外はメインスレッドに送られます。

スレッドの終了

スレッドの起動時に指定したブロックの実行が終了するとスレッドの実行も終了します。ブロックの終了は正常な終了も例外などによる異常終了も含みます。

例外発生時のスレッドの振る舞い

あるスレッドで例外が発生し、そのスレッド内で rescue で捕捉されなかった場合、通常はそのスレッドだけがなにも警告なしに終了されます。ただしその例外で終了するスレッドを Thread#join で待っている他のスレッドがある場合、その待っているスレッドに対して、同じ例外が再度発生します。

 Thread#joinの概要は以下。

スレッド self の実行が終了するまで、カレントスレッドを停止します。

begin
  t = Thread.new do
    Thread.pass    # メインスレッドが確実にjoinするように
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

また、以下の 3 つの方法により、いずれかのスレッドが例外によって終了した時に、インタプリタ全体を中断させるように指定することができます。

上記3つのいずれかが設定されていた場合、インタプリタ全体が中断されます。

スレッド終了時の ensure 節の実行

スレッド終了時には ensure 節が実行されます。これはスレッドが正常に終了する時はもちろんですが、他のスレッドから Thread#kill などによって終了させられた時も同様に実行されます。

メインスレッドの終了時の詳細に関しては 終了処理 を参照して下さい。

スレッドの状態

個々のスレッドは、以下の実行状態を持ちます。これらの状態は Object#inspectThread#status によって見ることができます。

p Thread.new {sleep 1} # => #<Thread:0xa039de0 sleep>
状態 概要 詳細
run 実行/実行可能 生成されたばかりのスレッドや Thread#run や Thread#wakeup で起こされたスレッドはこの状態です。 Thread#join でスレッドの終了を待っているスレッドもスレッドの終了によりこの状態になります。 この状態のスレッドは「生きて」います。
sleep 停止 Thread.stop や Thread#join により停止されたスレッドはこの状態になります。 この状態のスレッドは「生きて」います。
aborting 終了処理中 Thread#kill 等で終了されるスレッドは一時的にこの状態になります。この状態から停止状態(sleep)になることもあります。 この状態のスレッドはまだ「生きて」います。
dead 終了 Thread#kill 等で終了したスレッドはこの状態になります。この状態のスレッドはどこからも参照されていなければ GC によりメモリ上からなくなります。 この状態のスレッドは「死んで」います。

デッドロックの検出 @todo

メンバ抜粋

特異メソッド

DEBUG DEBUG= abort_on_exception abort_on_exception= current exclusive exit fork handle_interrupt kill list main new pass pending_interrupt? report_on_exception report_on_exception= start stop

インスタンスメソッド

[] []= abort_on_exception abort_on_exception= add_trace_func alive? backtrace backtrace_locations exit fetch group inspect join key? keys kill name name= pending_interrupt? priority priority= raise report_on_exception report_on_exception= run safe_level set_trace_func status stop? terminate thread_variable? thread_variable_get thread_variable_set to_s value wakeup
特異メソッド インスタンスメソッド 概要
new, start/fork - 生成&開始(initializeする/しない)
stop run/wakeup 停止。再起動(即/状態変更)
exit,kill exit,kill,terminate 終了(カレント/指定)
for i in 1..5
   Thread.new(i) {|t| p t }
   Thread.start(i) {|t| p t }
   Thread.fork(i) {|t| p t }
end
a = Thread.new { print "a"; Thread.stop; print "c" }
sleep 0.1 while a.status!='sleep'
print "b"
a.run
a.join
# => "abc"
th1 = Thread.new do
  begin
    sleep 10
    p 'これが表示される前にキルされるはず。'
  ensure
    p "this will be displayed"
  end
end
sleep 0.1
th1.kill

所感

 Rubyは並列実行できないのかな?

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux