Threadの話
最近Rubyでクローラを書いた。
なかなか気合いの入った動きをみせ、一晩で3Gバイトものデータをダウンロードしてくる。
また、それに比例して処理も遅くなる為、Threadを使うことにした。
RubyでのThreadはグリーンスレッド、つまり1つのカーネルスレッドに対して複数のユーザースレッドが動作している為、並行的には動作できるが、並列には動かない。つまり速くならない。
しかしクローラのようにボトルネックがネットワークとディスクIOの場合、待ち時間に他の処理ができるのでそこそこ有用です。
問題としてRubyのThreadはとにかく遅い。コストが高すぎ。スイッチング遅すぎ。
本来は
(1..10000).map { |e| Thread.start { e**2 } }.map{ |th| th.value }
みたいにThreadを使い捨てにしたい。楽だし。
でもリソースがもったいなさ過ぎな上に逆に遅くなる。Matzも「Threadをいじめないで」と言うくらいに。
エコな時代はソフトウェアにも優しくしないといけない為、簡単なThreadPoolを書いた。
require 'thread' class ThreadPool def initialize(size) @size = size @queue = Queue.new @threads = [] end def execute(&block) @queue.push(block) @threads << create_thread if @threads.size < @size end def shutdown until @queue.num_waiting == @threads.size sleep(0.01) end @threads.each { |th| th.kill } end private def create_thread Thread.start(@queue) do |q| while true f = q.pop f.call end end end end if __FILE__ == $0 pool = ThreadPool.new(3) 10.times do |n| pool.execute { puts "#{Thread.current}: #{n}"; sleep(0.1) } pool.execute { puts "#{Thread.current}: #{n}"; sleep(0.1) } end pool.shutdown end
やみくもにThreadを生成せずに使い回すことでRubyに優しくなります。
いつの間にか入ってる系のFastThreadを使うとより良いかも。
ThreadPoolを使えばいい感じなActor実装ができるかもしれない。RubyによるActor実装であるconcurrentはRubyに厳しすぎる。
ということでエコなプログラミングを心がけたいと思います。
クローラ書くときはsleep入れてサーバーにも優しくしよう。