concurrent-ruby1.0.5以前でのPromisesの使い方

# 要約
注意： 2018年11月5日にconcurrent-ruby 1.1がリリースされ、Promises APIはデフォルトで含まれるようになりました。

- [concurrent-ruby](https://rubygems.org/gems/concurrent-ruby)の1.0.5以前のバージョンにはPromises APIは含まれていない
- Promises APIを使いたいときは、[concurrent-ruby-edge](https://rubygems.org/gems/concurrent-ruby-edge) gemを追加する


# 動機
## Ruby on Railsアプリケーションのスレッド処理にconcurrent-rubyを使いたい

Ruby on Railsアプリケーション中でスレッドを使ったプログラミングをしています。

Ruby on Rails 自体ではスレッドを扱う時に、Ruby標準ライブラリの[Thread](https://ruby-doc.org/core-2.5.0/Thread.html)をそのまま使うのでなく、[concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby)を使っています。[^1][^2][^3]

[^1]: https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activejob/lib/active_job/queue_adapters/async_adapter.rb
[^2]: https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/actioncable/lib/action_cable/server/worker.rb
[^3]: https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activerecord/lib/active_record/type/type_map.rb

アプリケーションプログラム中でスレッドを扱う際も、同じライブラリを使った方が機能の重複が減り、保守性が高まるでしょう。

## concurrent-rubyの具体例

「非同期に処理を並行実行し、すべての処理の完了を待つ」処理をThreadで書くと次のようになります。

```rb
threads = [1, 2, 3].map { |val| Thread.start { pp val } }
threads.each(&:join)
```

これをconcurrent-rubyを使うと、次のように書けます。

```rb
tasks = [1, 2, 3].map { |val| Concurrent::Promises.future { pp val } }
Concurrent::Promises.zip(*tasks).value!
```

少し長くなります。ソースコードとしてはあまり嬉しくありません。

concurrent-rubyは、スレッドを管理するスレッドプールを保持しています。スレッドプールの上限サイズを指定することで、同時に実行されるスレッド数を制限できます。想定外の入力値によって大量のスレッドが作られ、メモリを使い切る事故を防止できます。スレッド生成数を制限する処理を自分で書くと面倒です。ありがたいです。

# concurrent-rubyの状況
## リリース予定の（複数形の）Promises

[concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby)は、Rubyの並行プログラミングをサポートするgemです。

色々な言語から並行プログラミングの仕組みを取り込んで来ました。

- Clojureから [Future](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Future.html)と[Delay](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Delay.html)を
- JavaScriptから [Promise](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promise.html)を
- [I-structures: data structures for parallel computing](https://dl.acm.org/citation.cfm?id=69562)から [IVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/IVar.html)を

取り込んで来ました。

他のプログラミング言語から来た人の期待に答えられる一方で、Rubyプログラマにはどういうスタイルで並行プログラミングを書けばよいかを示してきませんでした。

今、乱立した作法を統一した[Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html)という名前の新しいAPIを組み直しています。masterブランチのREADME、つまりGithubのトップページは、新しいPromisesを対象として説明しています。Deprecatedセクションには

> Promise: Similar to Futures, with more features. Replaced by Promises.

と書かれています。Promisesを使うべきと推奨しているように読めます。


## Promisesの例

例えば、非同期に処理を並行実行し、すべての処理の完了を待つ処理を（以前の）Promiseで書くと次のようになります。

```rb
promises = [1, 2, 3].map { |val| Concurrent::Promise.execute { pp val } }
Concurrent::Promise.zip(*promises).then{|val| valを処理する }
```
これは、Promises版の

```rb
tasks = [1, 2, 3].map { |val| Concurrent::Promises.future { pp val } }
Concurrent::Promises.zip(*tasks).value!
```

と似ていますが、戻り値が違います。

Promise版はPromiseが帰ってきます。処理が完了したときの処理は、thenブロックの中で`valを処理する`必要があります。同期的なRubyのコードにPromiseが帰ってきても、同期処理には戻れません。同期処理の一部を非同期処理に置き換える際に、非常に悩まされる部分です。

（新しい）Promisesには`value`/`value!`のブロッキングメソッドが追加され、これらのメソッドを呼ぶだけで、同期コンテキストに制御フローを戻せます。同期プログラミングが圧倒的に多いRubyに、必要な非同期処理はこれです！

# 問題
## 1.0.5以前のバージョンにはPromisesは含まれない

Promisesは、1.1からリリースされました。

[v1.0.5](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.0.5)にはPromisesは含まれていません。
v1.0.5を使っている環境で`Concurrent::Promises`を呼ぼうとすると

```
uninitialized constant Concurrent::Promises
```

定数が存在しない旨のエラーが出ます。

```rb
require 'concurrent/promises'
```

を実行して、モジュールを読み込もうとすると

```
No such file to load -- concurrent/promises.rb (LoadError)
```

ファイルがない旨のエラーが出ます。

concurrent-ruby gemでインストールされているフォルダ`/usr/local/bundle/gems/concurrent-ruby-1.0.5/lib/concurrent/`を確認すると、`promises.rb`というファイルはありません。（注意：gemがインストールされるフォルダは環境によって異なります。`bundle config path`コマンドで確認してください。）



## 回避策

Promises APIは突然生まれてきたものではありません。
[v1.1.0.pre1のリリースノート](https://github.com/ruby-concurrency/concurrent-ruby/releases)によると

> Promises are moved from concurrent-ruby-edge to concurrent-ruby

`moved from concurrent-ruby-edge`とあります。

[concurrent-ruby-edge](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib-edge/concurrent/edge/promises.rb) gemにpromises.rbが含まれています。
Gemifleに`concurrent-ruby-edge`を追加、`bundle`コマンドを実行し、ソースコードに

```rb
require 'concurrent/edge/promises'
```

を追加、`Concurrent::Promises`クラスを読み込めば（複数形の）Promisesが使えます！:tada:

concurrent-rubyのv1.1.0がリリースされた暁には、concurrent-rubyのバージョンをあげ、concurrent-ruby-edgeを消せば、対応できるでしょう。