上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
コミュニティ検索 »
JRuby | 2008/03/22(土) 03:06
Rubyでスクレイピングするツールを書いていたら、何万件とか順次処理じゃ遅すぎて待てない。 すでに6時間以上経過しているが、まだ1/5残ってる・・・。(使用メモリが増えないので継続できている)
まあ、他人のサイトにそんなに負荷かけるなよ、という話は置いといて。

というわけでスレッドを使おう → Javaで書こう → スクレイピング&DB保存処理を書き直し → orz

となったので、Javaで書くのはやめてJRubyで書くことにしました。 これならMechanize使ってるスクレイピング処理、ActiveRecord使ってるDB関連処理を書き直さず、Javaでネイティブスレッドの恩恵を受けられる!はず。


JRuby1.1RC3で試す。もうすぐ1.1リリースかな。

まずはJRubyでjava.lang.Threadを使うところからやってみよ。
これはJRubyのsamplesフォルダのthread.rbに書いてある。
x = Thread.new { sleep 0.1; print "x"; print "y"; print "z" }
a = Thread.new { print "a"; print "b"; sleep 0.2; print "c" }
x.join # Let the threads finish before
a.join # main thread exits...
なるほど、Thread.newにブロックを渡すとrun()で実行されるのね。
当然ながらこれは動く。

そもそもJavaでスレッドってどう書くんだっけ?という方はこちらがわかりやすい。

調べていたら以下のようなエントリを発見。
http://www.atdot.net/~ko1/diary/edit_comment.cgi?mode=long&year=2007&month=6&day=12
試しにここで紹介されてるコードも実行してみる。
str = ''
max = 1000
(1..4).map{|e|
  Thread.new(e){|ti|
    max.times{
      str << ti.to_s
    }
  }
}.each{|t| t.join}
p str

お、動くじゃん。このとき問題だった点は解消されているということかな?
と思ったら何度か実行したらエラーになった。
Exception in thread "Ruby Thread8970080" java.lang.ArrayIndexOutOfBoundsException
        at java.lang.System.arraycopy(Native Method)
        at org.jruby.util.ByteList.ensure(ByteList.java:187)
        at org.jruby.RubyString.modify(RubyString.java:243)
        at org.jruby.RubyString.cat(RubyString.java:577)
        at org.jruby.RubyString.append(RubyString.java:1037)
        at org.jruby.RubyString.concat(RubyString.java:1049)
・・・以下略
エラーの内容は若干違うけど、やっぱりダメかね。
RubyStringがスレッドセーフじゃないのかな?ということで文字列ではなく配列に結果を代入。
ary = []
max = 1000
(1..4).map{|e|
  Thread.new(e){|ti|
    max.times{
      ary << ti.to_s
    }
  }
}.each{|t| t.join}
p ary
でもエラー。今度はぬるぽ。
Exception in thread "main" java.lang.NullPointerException
        at org.jruby.RubyArray.inspectAry(RubyArray.java:1099)
        at org.jruby.RubyArray.inspect(RubyArray.java:1125)
        at org.jruby.RubyArrayInvoker$inspect_method_0_0.call(Unknown Source)
        at org.jruby.internal.runtime.methods.JavaMethod$JavaMethodZero.call(JavaMethod.java:82)
        at org.jruby.RubyClass.invoke(RubyClass.java:236)
        at org.jruby.javasupport.util.RuntimeHelpers.invoke(RuntimeHelpers.java:314)
        at org.jruby.RubyObject.callMethod(RubyObject.java:466)
        at org.jruby.RubyKernel.p(RubyKernel.java:334)
        at org.jruby.RubyKernelInvoker$p_s_method_0_0.call(Unknown Source)
        at org.jruby.internal.runtime.methods.JavaMethod$JavaMethodNoBlock.call(JavaMethod.java:63)
        at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:78)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.cacheAndCall(CallSite.java:147)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.call(CallSite.java:307)
        at ruby.C_3a_.Users.kazuhiro.t2.__file__(t2.rb:10)
        at ruby.C_3a_.Users.kazuhiro.t2.load(t2.rb)
        at org.jruby.Ruby.runScript(Ruby.java:510)
        at org.jruby.Ruby.runNormally(Ruby.java:430)
        at org.jruby.Ruby.runFromMain(Ruby.java:310)
        at org.jruby.Main.run(Main.java:141)
        at org.jruby.Main.run(Main.java:88)
        at org.jruby.Main.main(Main.java:79)
よくわからんが、受取側のオブジェクトに排他がかけれてない?samplesのやつもvoidな処理だしなあ。JRubyのJIRAを見たらこういうバグも上がっていた。あんまり関係ないかもだけど。
Deadlock With ReentrantLock and Java Integration

さて、でもせっかくなのでRunnableで実装してみよっと。上のコードも動く時は動くし。
でもどうにも情報が見つからず。当てずっぽうでやってみる。

めんどいので結論。
(コードハイライトがおかしくて、一部このままでは動きません。実際のコードはview plainという灰色のリンクを押してください)
require 'java'
class Test
  include java.lang.Runnable
  def initialize(str = "a")
    @str = str
  end
  
  def run
    100.times {
      print @str
    }
  end
end

runnables = []
runnables << Test.new
runnables << Test.new("b")
runnables << Test.new("c")
runnables << Test.new("d")
runnables.map{|runner|
  Thread.new(runner){|r| r.run}
}.each{|t| t.join}
いろいろ試した結果、なぜかJavaではvoidのはずのRunnable#run()で結果を返せたり。 Thread.newの引数にはブロックが必要なのでRunnableの意味ないなー、と思っていたらrun()をhoge()とかに変えたり、そもそもRunnableのincludeやめても動いた。Thread.newには任意のオブジェクトを渡せるみたい。Runnable意味ねー。
class Test
  def initialize(str = "a")
    @str = str
  end
  
  def hoge
    100.times {
      print @str
    }
  end
end

runnables = []
runnables << Test.new
runnables << Test.new("b")
runnables << Test.new("c")
runnables << Test.new("d")
runnables.map{|runner|
  Thread.new(runner){|r| r.hoge}
}.each{|t| t.join}
これって、もし既存のクラスの処理をマルチスレッドで実行しようと思ったら、Thread.newに渡してブロックでメソッド呼んだら終了ってこと?すごいじゃん!(ちゃんと動けば・・・)

これから実装するなら、Thread継承でもOK。
require 'java'
class ThreadTest < java.lang.Thread
  def initialize(type = "a")
    @type = type
  end
  
  def run
    100.times {
      print @type
    }
  end
end

threads = []
threads << ThreadTest.new
threads << ThreadTest.new("b")
threads << ThreadTest.new("c")
threads << ThreadTest.new("d")
threads.each{|t| t.start}
threads.each{|t| t.join}
ここでrun()をhoge()とか違うメソッド名にすると何も出力されなくなったので、確かにThread#run()が実行されているみたい。
とりあえず動かすことはできたので、試しにスクレイピング&DB保存のやつで実践してみよっと。 もちろんクリティカルな処理には使わない方がよいのは自明なので、あしからず。



お、ちょうど処理も終わった。




ちなみに、ようやくコードのハイライト表示をできるようにしました。でも一部表示がおかしいな。(strstrとかなってる。これじゃ動かんな)
参考
http://blog.37to.net/2007/06/syntax_highlighter/
スポンサーサイト
トラックバック
この記事のトラックバックURL
http://completemirage.blog55.fc2.com/tb.php/55-f527ab72
この記事にトラックバックする(FC2ブログユーザー)
コメント







非公開コメント
プロフィール
 

miyazima

Author:miyazima
常に変化を好み、面白いことを探しています。次の次は?

カレンダー
 
04 | 2017/05 | 06
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
カウンター
 
天気予報
 

-天気予報コム- -FC2-
ブログ内検索
 
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。