はじめに
最近、複数メソッドを並列で処理するコーティングを行い、concurrent-rubyについて学んだので簡単にまとめました。
concurrent-rubyとは
concurrent-rubyは、並行処理のデザインパターンを元に作られたライブラリ。
サンプル
concurrent-rubyは、様々な作法が存在しますが、ネットで検索すると、単数形のPromiseやFutureなどと色々な書き方が出てきます。最近、Promisesで統一されつつあるらしいので、Concurrent::Promises
のサンプルを紹介します。
まずは、並列に実行したいProcオブジェクトを作成するメソッド(p1、p2、p3)を準備。今回は、それぞれのメソッドの中に、1secずつずらしたsleepを設けます。
def p1
lambda {
start_time = Time.now
sleep 1
return "p1 => start time: #{start_time}, finish_time: #{Time.now}s"
}
end
def p2
lambda {
start_time = Time.now
sleep 2
return "p2 => start time: #{start_time}, finish_time: #{Time.now}s"
}
end
def p3
lambda {
start_time = Time.now
sleep 3
return "p3 => start time: #{start_time}, finish_time: #{Time.now}s"
}
end
pry(main)> p1
=> #<Proc:0x000055bb4bf3ad48@(pry):68 (lambda)>
pry(main)> p2
=> #<Proc:0x000055bb4bf16df8@(pry):75 (lambda)>
pry(main)> p3
=> #<Proc:0x000055bb4bef7660@(pry):91 (lambda)>
それぞれの処理を実行するスレッドを作成します。以下の状態では、pending
となっており、まだ処理は実行されていません。
tasks = [p1, p2, p3].map { |p| Concurrent::Promises.future { p.call } }
=> [#<Concurrent::Promises::Future:0x000055bb4be2aac0 pending>,
#<Concurrent::Promises::Future:0x000055bb4be29b20 pending>,
#<Concurrent::Promises::Future:0x000055bb4be289f0 pending>]
zip
とういうメソッドが用意されているのでそれを使用すると、同時にスレッドが実行され、それらの返り値が配列にまとめられます。
pry(main)> Concurrent::Promises.zip(*tasks).value!
=> ["p1 => start time: 2019-06-12 18:23:01 +0900, finish_time: 2019-06-12 18:23:02 +0900s",
"p2 => start time: 2019-06-12 18:23:01 +0900, finish_time: 2019-06-12 18:23:03 +0900s",
"p3 => start time: 2019-06-12 18:23:01 +0900, finish_time: 2019-06-12 18:23:04 +0900s"]
start time
は実行時刻。同じ時刻(2019-06-12 18:23:01 +0900)から同時に実行されたことがわかります。また、finish_time
は1秒ずつずれています。逐次処理の場合は、sleepタイム毎に時刻がずれるため、これが並列処理で実行されていることがわかります。
まとめ
簡単にですが、Rubyの並列処理についてまとめました。今回は、非常に簡単な例ですが、まともに使おうとするとかなり手こずると思います。Railsアプリケーションに組み込もうとして実装を試みましたが、実装が複雑化してデバックがしにくくなるため、導入を断念しました。正直、Ruby自体が並列処理に向いてなさそうです。Railsアプリケーションならば、Sidekiqなどを使用して無理せず実装した方が良さそうですね。