Concurrent::Promises.zipとFuture.delay

  • このエントリーをはてなブックマークに追加

concurrent-rubyの使い方を調べていて、こちらのページを見つけたのですが、解説が正しくなかったので、正しい挙動をメモ。

このQiitaのページでは、以下のように記載があります。

zipとういうメソッドが用意されているのでそれを使用すると、同時にスレッドが実行され、それらの返り値が配列にまとめられます。

しかし、Concurrent::Promises.futurefuture_onのshortcutであり、future_oneval on immediatelyであるので、futureを作った時点で実行が始まります。そのため、zipとは関係なく、threadの状態はfullfilledになります。

Constructs new Future which will be resolved after block is evaluated on default executor. Evaluation begins immediately.

zipメソッドで処理を発火したい場合は、delayを使います。

sample program
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
threads = (1..5).map do |i|
Concurrent::Promises.delay(i) do |i|
n = rand(5) + 1
puts "[#{i}] start: #{Time.now} / sleep=#{n}"
sleep n
puts "[#{i}] finished: #{Time.now}"
"result: #{i}"
end
end

sleep 3
puts "before zip: #{Time.now}"
Concurrent::Promises.zip(*threads)
.then{|*hashes| hashes.reduce(&:merge) }
.value

実行結果(delay)

result by delay
before zip: 2021-04-11 14:56:35 +0900            # zipで発火するまでスレッドは実行されていない
[5] start: 2021-04-11 14:56:35 +0900 / sleep=3
[4] start: 2021-04-11 14:56:35 +0900 / sleep=3
[3] start: 2021-04-11 14:56:35 +0900 / sleep=4
[2] start: 2021-04-11 14:56:35 +0900 / sleep=5
[1] start: 2021-04-11 14:56:35 +0900 / sleep=4
[4] finished: 2021-04-11 14:56:38 +0900
[5] finished: 2021-04-11 14:56:38 +0900
[1] finished: 2021-04-11 14:56:39 +0900
[3] finished: 2021-04-11 14:56:39 +0900
[2] finished: 2021-04-11 14:56:40 +0900
```

実行結果(futureの場合)

result by future
ruby aaa.rb 
[1] start: 2021-04-11 14:57:14 +0900 / sleep=4
[2] start: 2021-04-11 14:57:14 +0900 / sleep=5
[3] start: 2021-04-11 14:57:14 +0900 / sleep=4
[4] start: 2021-04-11 14:57:14 +0900 / sleep=1
[5] start: 2021-04-11 14:57:14 +0900 / sleep=1
[5] finished: 2021-04-11 14:57:15 +0900
[4] finished: 2021-04-11 14:57:15 +0900
before zip: 2021-04-11 14:57:17 +0900 # ▲zipの前にスレッド実行されている
[3] finished: 2021-04-11 14:57:18 +0900
[1] finished: 2021-04-11 14:57:18 +0900
[2] finished: 2021-04-11 14:57:19 +0900

参考情報