2012年3月6日火曜日

Rubyでスレッド処理をしてみた

このエントリーをはてなブックマークに追加
Rubyでスレッド処理をしてみました。ちょっとでも使えるようになると、プログラミングが楽しくなるのが、スレッド処理かなと思います。

今回のサンプルプログラムでは、メインとなるプログラムのスレッドのほかに、2つのスレッドを作成します。それぞれのスレッドで文字列を並行して出力させ、実行結果がどうなるか見てみます。

thread_sample1.rb
require 'thread'

# 1つ目のスレッド(作成後、すぐに走り始める)
Thread.new do
  5.times do
    puts "Thread1"; $stdout.flush
  end
end

# 2つ目のスレッド(作成後、すぐに走り始める)
Thread.new do
  5.times do
    puts "Thread2"; $stdout.flush
  end
end

# 上記2つのスレッドの実行が終わるのを待つ
Thread.list.each { |t| t.join unless t == Thread.current }

puts "Main Thread"


実行結果
(※同じプログラムでも、実行環境によって実行結果が変わると思います。)
$ ruby thread_sample1.rb
Thread1
Thread2Thread1

Thread2
Thread1
Thread2
Thread2Thread1

Thread1Thread2

Main Thread


プログラムではputsを使って文字列を1行単位で出力させている($stdout.flushも敢えて実行させている)はずですが、実際の実行結果では、なぜか"Thread2Thread1"と続けて文字列が出力されました。このあたりがスレッド処理特有の現象で、1つのメインプログラムだけのシングルスレッドではなかなか予想できないことが、起きるようになります。

この"Thread2Thread1"と出力された原因は、おそらく、Thread2が出力バッファからflushされる前に、Thread1が出力バッファに書きこまれたためです。よって、出力部分をMutexでロックしてflushまでをアトミックな処理にするようプログラムを変更してみます。

thread_sample2.rb
require 'thread'

# Mutex
lock = Mutex.new

# 1つ目のスレッド(作成後、すぐに走り始める)
Thread.new do
  5.times do
    lock.synchronize { puts "Thread1"; $stdout.flush }
  end
end

# 2つ目のスレッド(作成後、すぐに走り始める)
Thread.new do
  5.times do
    lock.synchronize { puts "Thread2"; $stdout.flush }
  end
end

# 上記2つのスレッドの実行が終わるのを待つ
Thread.list.each { |t| t.join unless t == Thread.current }

puts "Main Thread"


実行結果
(※同じプログラムでも、実行環境によって実行結果が変わると思います。)
$ ruby thread_sample2.rb
Thread1
Thread1
Thread1
Thread1
Thread1
Thread2
Thread2
Thread2
Thread2
Thread2
Main Thread

今度は、putsからflushまでがアトミックな処理として行われたことを反映して、Thread1, Thread2の文字列出力が1行ごとに出力されています。スレッドを使うと予期せぬ現象が起こり、バグになりやすいため扱いが難しいところもありますが、その分使いこなせるようになるとおもしろいと思います。

参考ページ
ミューテックス - Wikipedia -
アトミック性 - Wikipedia -
スレッドセーフ - Wikipedia -

0 件のコメント:

コメントを投稿