コメントで重要な指摘を貰っているので、そちらも参照のこと
遅くなってしまって、済みません。
以前自分の日記に書いた内容の焼き直しですが、Groongaアドベントカレンダーの四日目です。四日目です!(エントリーの作成日時は見ないでくださいね)
複数のスレッドからRroonga(を通したGroongaデータベース)を触る時には、同時にDBを開かないように気を付けなくてはなりません(とは言え、そもそもそういう状況になるのがよくないので、見直したほうがいいです)。
例えばこんなユーティリティ関数を作って
def open
Groonga::Database.open @db_file do |db|
yield db
end
end
複数のスレッドから触ると……
$stderr.sync = true
threads = []
1000.times do |i|
$stderr.print "#{i}: start\n"
threads << Thread.new(i) {
$stderr.print "#{i}: open\n"
open do |db|
$stderr.print "#{i}: before\n"
Groonga['Items'].add i.to_s
$stderr.print "#{i}: after\n"
end
}
end
threads.each &:join
ほとんどの場合、最後まで実行されず途中で例外でストップすると思います。
これは、データベースを触る時にちゃんとロックすることで回避することができます。
require 'monitor'
# :
# monitorのセットアップ
# :
threads = []
1000.times do |i|
$stderr.print "#{i}: start\n"
threads << Thread.new(i) {
$stderr.print "#{i}: open\n"
synchronize do
open do |db|
$stderr.print "#{i}: before\n"
Groonga['Items'].add i.to_s
$stderr.print "#{i}: after\n"
end
end
}
end
threads.each &:join
ということを自分の日記に書いたら、須藤さんからこんな突っ込みを頂きました。
https://twitter.com/ktou/statuses/339355624703930368
@KitaitiMakoto Groonga["..."]は普段使いの時に便利になるためのショートカットで、共通のGroonga::Contextを使うんです。普通に使う分には便利にするけど、複雑なことをしたいならそこは自分でカバーしてね!という方針です!
というわけで、Groonga::Contextを調べてみました。
ドキュメントを読んでも、使い方がはっきりとは分からないのですが、open_database
というのが怪しいのでこれを使ってopen
を書き換えてみます。
def open
Groonga::Context.new.open_database @db_file do |db|
yield db
end
end
すると、おお、ちゃんと完走する。
$ ruby groonga-context.rb
:
994: after
995: before
995: after
しかも、monitorを使った場合の40%くらい速い!
$ time ruby groonga-monitor.rb
:
real 0m5.404s
user 0m4.512s
sys 0m0.768s
$ time ruby groonga-context.rb
:
real 0m3.134s
user 0m2.420s
sys 0m0.620s
というわけで、(なるべくそんなことしないほうがいいと思うけど)マルチスレッド環境でRroongaを使う場合はGroonga::Context
を使うようにしましょう。