アプリケーションに合ったExecutionContextを使う

scalaではFutureなどの裏側ではExecutionContextが動いています。ということはExecutionContextの使い方がいまいちだとFuture周りで問題が起きることになります。

よく起きる問題の1つとして、標準のExecutionContextがいまいちだった、というのがあると思います。 標準のExecutionContextはダメというか、適さないパターンがあるのですが、あまりよく考慮されずに使われてしまう印象があります。 まあ悪いのはコンパイラの親切すぎるエラーメッセージでしょう。

scala> import scala.concurrent.Future
import scala.concurrent.Future

scala> Future { 1 + 1 }
<console>:9: error: Cannot find an implicit ExecutionContext. You might pass
an (implicit ec: ExecutionContext) parameter to your method
or import scala.concurrent.ExecutionContext.Implicits.global.
              Future { 1 + 1 }
                     ^

scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global

scala> Future { 1 + 1 }
res2: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@2bbaf4f0

scala> res2.foreach(println)
2

このようにScalaでFutureを使おうとすると、ExecutionContextがないからimport scala.concurrent.ExecutionContext.Implicits.globalをどうとか言われるので、とりあえず使っとくかーみたいな気分になっちゃいます。

標準のExecutionContextの特徴

まず、標準のExecutionContextはスレッド数がそんなに多くありません。2.11では最大でもコア数分のスレッドしか立ち上がりません。 それではデッドロックが起きやすいので2.12ではコア数+256までとなっているようですが、用途によってはもっと増やしたいですね。

それから、標準のExecutionContextはForkJoinPoolを使って実装されています。ForkJoinPoolは

It is designed for work that can be broken into smaller pieces recursively

https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

とのことで、parallel collectionとかには向いてそうだけどWebアプリケーションとかには向かなそうですね(ダメってこともないだろうけど)。 みなさんの用途には合ってるでしょうか?

アプリケーションに合ったExecutionContextを使う

標準のExecutionContextをやみくもに使うのはやめて、アプリケーションに合ったExecutionContextを使いましょう。 作り方はExecutionContext.fromExecutor, ExecutionContext.fromExecutorServiceを使えばjava.util.concurrentで作ったExecutor/ExecutorServiceを元にExecutionContextを作る、で大抵事足りる気がします。

Futureを使う時にJavaのExecutorServiceを利用する - CLOVER

フレームワークを使っている場合はフレームワークが独自のスレッドプールを用意していることがあります。 Playの場合はakkaのスレッドプールを利用していて、import play.api.libs.concurrent.Execution.Implicits._ することで有効になります。 それから、ドキュメントのスレッドプールについての箇所は必読です。

ライブラリのコードに標準のExecutionContextを含めない

Futureなどを用いたライブラリを作るときは、標準のExecutionContextをハードコードしないように注意してください。 ExecutionContextはメソッドの引数経由で渡すなど、なんらかの形で利用者が調整できるようにしましょう。

まとめ

ScalaではFutureなどのおかげで気軽に非同期処理などが利用されますが、その裏にはスレッドプールであるExecutionContextがいます。 しかし、ExecutionContextはimplicitパラメータとして暗黙のうちに引き回されるため、よくも悪くもあまり意識されません。困ったもんですね。

ExecutionContextついてはその実装を見てみることも参考になりますが、やはりJavaのスレッド周りの知識が不可欠です。 Scalaを始めたけれどJavaはよく分からないという人はjava.util.concurrent(便利)の使い方を学んでおくと雰囲気がつかめるかなと思いました。