inohilog

/var/log/inohiro

Ruby でマルチスレッドプログラミング

ある問題で、Redis からデータを読んで、中身に書いてあることをやって(sleepするとか)というものがあった。その発展系では、並列でやるようにしろと言うものだった。だいたい処理を並列にさせたい時は、parallel gem を使っている。とっても簡単で、仕事でもよく使っている。

任意のスレッド数、またはプロセス数はを指定して、#map #each などのメソッドを使ってブロックで囲むと、内側が並列で動作するようになる。本当に簡単なのでおすすめ。

require 'parallel'

Parallel.each((1..12), in_threads: 3) do |i|
# Parallel.each((1..12), in_processes: 3) do |i|
  puts "[Worker: #{Parallel.worker_number}] #{i}"
end
$ ruby paralle.rb
[Worker: 1] 1
[Worker: 0] 3
[Worker: 2] 2
[Worker: 1] 4
[Worker: 1] 7
[Worker: 0] 5
[Worker: 1] 8
[Worker: 2] 6
[Worker: 0] 9
[Worker: 1] 10
[Worker: 2] 11
[Worker: 0] 12

ここまで良いんだけど、parallel gem を使える時は予め処理する対象が配列になってる必要があるようだ。今回の用途だと、複数のスレッドを起動して、それぞれが Redis に読みに行くというのをやりたかったので、並列に実行する箇所の前では、処理対象のオブジェクトが用意できなかった。

[追記] うーん、適当に配列を作って渡したら動いたな... コード書いてる時はなぜかうまく動いてくれなかったんだが...

Parallel.each((0..10), in_threads: 10) do |i|
  Worker.new(i).exec_job
end

そこで、concurrent-rugy gem はどうだろうと見てみたが、良い感じに当てはまるものを見つけられなかった。今思うと FuturePromise を使うのが良かったかもしれないが、そんなに複雑なことがしたいわけでもなかった。

最終的に、今回は Thread をそのまま使うことになった。Thread をそのまま使うのはなんとなく避けてきたが、簡単に動いて便利(そのうち罠にハマりそう)。

threads = (1..num_parallel).map do |num|
  Thread.new do
    Worker.new.exec_job
  end
end
threads.map(&:join)

なんか良さそうな記事を見つけた。でもちょっと古いかも

www.toptal.com