アプリケーションに合った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(便利)の使い方を学んでおくと雰囲気がつかめるかなと思いました。