Future
マルチスレッドパターン続き、今回はFutureパターンです。
これは「戻り値の準備」と「戻り値の利用」を分けましょう、というものです。
メインスレッドは一旦プロキシを受け取っておいて、プロキシに対応する実際のデータは別スレッドで走って作りましょう、ということですね。
サンプル
Rubyで実装したFutureパターンのサンプルソースです。
module FT require 'thread' require 'monitor' class FutureData include MonitorMixin def initialize() super() @realdata = nil @ready = false @cond = self.new_cond end def get_content return "future data:#{self}" unless @ready @realdata.get_content end def set_content synchronize do return if @ready @realdata = RealData.new @ready = true @cond.signal end end end class RealData def initialize puts "#{Thread.current}:start: make real data" sleep(5) puts "#{Thread.current}:end: make real data" end def get_content "real data :#{self}" end end class Host def initialize end def request data = FutureData.new Thread.new do data.set_content end data end end end
呼び出し側。dataの取得は一度、それに対して三つのスレッドがget_contentを呼び出しています。
require 'future.rb' host = FT::Host.new data = host.request for i in 1..3 do Thread.new do loop do puts "get #{data.get_content}:#{Thread.current}" sleep(2) end end end sleep(7)
結果。
#<Thread:0xb7f59224>:start: make real data get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f590bc> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f58ff4> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f58f2c> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f58ff4> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f590bc> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f58f2c> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f58ff4> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f590bc> get future data:#<FT::FutureData:0xb7f5929c>:#<Thread:0xb7f58f2c> #<Thread:0xb7f59224>:end: make real data get real data :#<FT::RealData:0xb7f59120>:#<Thread:0xb7f58ff4> get real data :#<FT::RealData:0xb7f59120>:#<Thread:0xb7f590bc> get real data :#<FT::RealData:0xb7f59120>:#<Thread:0xb7f58f2c>
前半のget_contentではFutureDataに記載された処理が返されているのに対し、後半はRealData#get_contentが返されているのが分かります。
今回のFutureData#get_contentは「できていなかったらすぐに返す」という動作のため、synchronizeは外してあります。
ここがもし「できるまで待つ」だった場合、このメソッドをsynchronizeにしなくてはいけないでしょう。
サンプルケース RSSゲッター
折角なので、このFutureパターンを利用したプログラムをと思い、RSSをマルチスレッドで走りながら取得するプログラムを作ってみました。
module GETRSS require 'thread' require 'monitor' require 'rss/2.0' require 'stringio' class FutureData include MonitorMixin def initialize() super() @realdata = nil @ready = false @cond = self.new_cond end def get_content synchronize do @cond.wait_until{ @ready } @realdata.get_content end end def set_content(req) synchronize do return if @ready @realdata = RealData.new(req) @ready = true @cond.signal end end end class RealData def initialize(req) puts "#{Thread.current}:get [#{req}]" sio = StringIO.new begin open(req) do |h| raise "not found '#{request}'" unless h response = h.read rss = RSS::Parser.parse(response, false) title = rss.channel.title rss.items.each_with_index do |item, i| sio.puts "#{title}(#{i}):#{item.title}" if i < 5 end end rescue => exc sio.puts "Failed:#{exc}" end sio.rewind @data = sio.read puts "#{Thread.current}:get rss...done" end def get_content @data end end class Host def initialize end def request(req) data = FutureData.new Thread.new do data.set_content(req) end data end end end
呼び出し側。
require 'future.rb' host = GETRSS::Host.new requests = [ 'http://headlines.yahoo.co.jp/rss/rps_dom.xml', 'http://news.goo.ne.jp/rss/topstories/gootop/index.rdf', 'http://rss.rssad.jp/rss/livedoornews/topics/rss.xml' ] list = [] requests.each do |r| list << host.request(r) end list.each do |data| puts data.get_content end
結果。
<Thread:0xb7e42840>:get [http://headlines.yahoo.co.jp/rss/rps_dom.xml] <Thread:0xb7e0f6e8>:get [http://news.goo.ne.jp/rss/topstories/gootop/index.rdf] <Thread:0xb7e09108>:get [http://rss.rssad.jp/rss/livedoornews/topics/rss.xml] <Thread:0xb7e0f6e8>:get rss...done <Thread:0xb7e42840>:get rss...done Yahoo!ニュース - 国内 - レスポンス(0):マツダレンタカー、iDとEdyでの支払いが可能に(レスポンス)5日22:10 Yahoo!ニュース - 国内 - レスポンス(1):多機能型消防車、登場…消防団に助っ人(レスポンス)4日23:10 Yahoo!ニュース - 国内 - レスポンス(2):BMWジャパン、最長5年間のメンテナンスパッケージを導入(レスポンス)31日21:10 Yahoo!ニュース - 国内 - レスポンス(3):マツダレンタカー、セラピードッグの育成・派遣に協力(レスポンス)31日20:40 Yahoo!ニュース - 国内 - レスポンス(4):【ガソリン国会】暫定税率つなぎ法案 2委員会で成立したが…(レスポンス)30日21:10 注目のニュース - gooニュース(0):中国TV、日本調査が安全お墨付き 注目のニュース - gooニュース(1):「なぜ株大幅安か疑問」官房長官 注目のニュース - gooニュース(2):橋下氏、出産・子育て支援は凍結 注目のニュース - gooニュース(3):MSとヤフーの強みは「検索以外」 注目のニュース - gooニュース(4):実現しなかったオバマ圧勝、なぜ <Thread:0xb7e09108>:get rss...done livedoor NEWS - 主なトピックス(0):"政治家になる為行列"と批判 livedoor NEWS - 主なトピックス(1):雅子さまに同情で日本人反発? livedoor NEWS - 主なトピックス(2):"鼻につく"OLに嫌われる大学 livedoor NEWS - 主なトピックス(3):妻の上に座って圧死させる livedoor NEWS - 主なトピックス(4):帝拳ボクサー 韓国男児を救う
RSSの取得ログとRSS表示がまざって表示されているところから、取得と表示がマルチスレッドで実行されているのがわかります。