hitode909の日記

趣味はマリンスポーツですの日記です

Rubyで並列にopenする

外部のリソースを複数openするようなとき,1つずつ順番にやっていると,時間がかかってしまう.

require 'open-uri'
urls = %w{ a b d f g graph}.map{ |service|
  "http://#{service}.hatena.ne.jp/hitode909/"
}
contents = urls.map{ |url|
  open(url).read
}

これは17秒かかる.


並列に実行して,それらが終わるのを待つようにすると,実行時間を短縮できる.
いいライブラリを使えばいい感じにできそうだけど,1つずつThreadを作って,あとで順番にjoinすると,簡単にできる.

require 'open-uri'
urls = %w{ a b d f g graph}.map{ |service|
  "http://#{service}.hatena.ne.jp/hitode909/"
}
contents = []
threads = urls.map{ |url|
  Thread.new{
    contents << open(url).read
  }
}
threads.each{ |thread|
  thread.join
}

これなら7秒で終わる.


1つだけ重いサーバーがあったりするとなかなか終わらなくて困ったりする.
こうすると,最悪3秒待って,終わってなくても次の処理に進める.
タイムアウトして処理が打ち切られるわけではなくて,あとで,contentsがだんだん増えていったりするので,気をつけないとおかしくなる.

require 'open-uri'
require 'timeout'
urls = %w{ a b d f g graph}.map{ |service|
  "http://#{service}.hatena.ne.jp/hitode909/"
}
contents = []
threads = urls.map{ |url|
  Thread.new{
    contents << open(url).read
  }
}
begin
  timeout(3) {
    threads.each{ |thread|
      thread.join
    }
  }
rescue Timeout::Error
end

取得できないのもあるけど,とりあえず3秒で終わる.なんでもいいから3秒分ほしいときとか.


逆に,urlsがめちゃくちゃ多いときは,一度に大量のリクエストが飛んでしまって,DoS攻撃みたいになってしまうので,気をつける必要がある.そういう場合は1個ずつ取得するほうがよさそう.