それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Rubyでマルチスレッド 11 #Future

Ruby結城浩せんせいのJavaマルチスレッド本 その11 Future

引換券

番号札を持ってお席でお待ちください

重要なのは、「席でお待ちください」ってことで。席で待ってたら届くと言うこと。必要になれば取りに行って待つことも出来るが。。。(balking/guarded suspension)

データを作るための別スレッド

Dataを処理してRealDataに変えて欲しい。しかし時間が掛る。そこで、FutureDataを貰っておく。自分は別のことをしながら、別スレッドがデータを作るのを待つ。

rubyで作ったFuture

#!/usr/bin/env ruby
# Future 
require "thread"
class RealData
  attr_reader :content
  def initialize count ,c
    puts "        making RealData( #{count}, #{c} )"
    @content = (1..count).map{sleep(100.to_f/1000); c}.join
  end
end
class FutureData
  @real_data = nil
  @ready = false
  def initialize
    @m = Mutex.new
    @cv = ConditionVariable.new
  end
  def set_real_data data
    @m.synchronize {
      return if( @ready )
      @real_data = data
      @ready = true
      @cv.broadcast
    }
  end
  def contents
    @m.synchronize{
      #while(!@ready) do @cv.wait(@m) end # Guraded Suspention
      return "まだ準備中よ。" if !@ready # Balking
      @real_data.content
    }
  end
  def to_s() contents end
end
class Host
  def request count, str
    future= FutureData.new
    puts "  request ( #{count}, #{str} ) BEGIN"
    t = Thread.new{
      data = RealData.new count,str
      future.set_real_data data
    }
    puts "  request ( #{count}, #{str} ) END"
    return future,t
  end
end
#STDOUT.sync = true
puts "main BEGIN"
host = Host.new
threads =[
  host.request(10, "A"),
  host.request(20, "B"),
  host.request(30, "C"),
]
puts "main END"


# 作り捨てたスレッドを待つ 
# Ruby のスレッドはMainが終了すると他も巻き込むのでここで待つ
# 
# スレッドが終ってからFutureを表示してみる
#    Guraded Suspention の場合 Futureを読み出しでブロックし、スレッド終了待つ
#    Balking            の場合 join で スレッド終了を待つ
threads.map{|r|r[0]}.each{ |v| puts "data = "+ v.to_s }
threads.map{|r|r[1]}.each{ |t|t.join}
threads.map{|r|r[0]}.each{ |v| puts "data = "+ v.to_s }

# Ajaxを状況に応じて表示変えるのに使われてたり?・・・
#   callback
# 終るまで、「処理中...」を表示したり
# 

Baklingの場合

Baklingの場合は、とりあえず適当な値を返しておいて、後で取りに来てね。

  def contents
    @m.synchronize{
      return "まだ準備中よ。" if !@ready # Balking
      @real_data.content
    }

guarded suspensionの場合

データが必要なのでWaitしてじっと待つ。

  def contents
    @m.synchronize{
      while(!@ready) do @cv.wait(@m) end # Guraded Suspention
      @real_data.content
    }
  end

応用例

画像変換など時間が掛る処理の場合、「準備中・・・・・・・・・・」を表示しておき、ユーザーからの操作を受け付けたり。準備中画像を表示しておくとか。データクラスをNewしたタイミングで使うとは限らない。CPU時間は変わらないが、I/O時間が変わるのでその分だけ恩恵はある。

real_data/future_dataを分ける意図

分ける理由は、マルチスレッド考慮の有無。real_dataにはマルチスレッド関連のモノが入っていない。future_dataがマルチスレッドのメソッドを実装していて、安全性を担保している。real_dataは既存のモノで構わない。