コミケ告知

サークル活動の詳細は circle タグの記事へ。
2014年10月13日月曜日

Scalaの実行コンテキストとFuture, blocking

ScalaのFutureというのは、下から(プロセッサに近い方からの)目線でいえば、処理をスレッドプールに丸投げする仕組みです。.NETにも似たような仕組みがありますね。

参考:Scala公式ドキュメント Futureの項目 (en / jp)

Futureの実行環境として、Scalaではscala.concurrent.ExecutionContext.Implicits.global というものがあり、implicitなExecutionContextとして、デフォルトでよさげなものを用意してくれます。このExecutionContextの挙動を確かめるために、ちょっとしたコードを書きました。
基本は、第一引数で指定した数のFutureを生成して走らせるだけです。Future内では、実行タイミングとスレッド番号を表示します。
% scala FutureBehaviorChecker.scala 1
Processors: 8
Thread active: 3
Current Thread: 1
Mode: sleep without blocking{}.
Finished: 00 [   27 ->   228] at  10
% scala FutureBehaviorChecker.scala 6
Processors: 8
Thread active: 8
Current Thread: 1
Mode: sleep without blocking{}.
Finished: 00 [   28 ->   228] at  10
Finished: 05 [   31 ->   231] at  14
Finished: 02 [   31 ->   231] at  13
Finished: 03 [   31 ->   231] at  16
Finished: 04 [   31 ->   231] at  17
Finished: 01 [   31 ->   231] at  12

ExecutionContext.Implicits.globalの挙動

スレッドプール内のスレッド数は、プロセッサ数と同じだけの上限を持つようです。
% scala FutureBehaviorChecker.scala 10 
Processors: 8
Thread active: 9
Current Thread: 1
Mode: sleep without blocking{}.
Finished: 00 [   33 ->   234] at  10
Finished: 04 [   37 ->   237] at  14
Finished: 03 [   37 ->   237] at  13
Finished: 05 [   37 ->   237] at  15
Finished: 02 [   37 ->   237] at  12
Finished: 01 [   36 ->   236] at  11
Finished: 06 [   37 ->   237] at  16
Finished: 07 [   37 ->   237] at  17
Finished: 08 [  246 ->   446] at  10
Finished: 09 [  246 ->   446] at  14
9個目以降は、全スレッドがブロックしているので詰まってますね。
まあデフォルトなんてこんなものか。と自前のExecutorContext指定の方法を調べかかりましたが、そこに手を出す前にもっと簡単な方法がありました。

blockingの指定

本家のドキュメント内ではさらりと流されていますが、ブロッキングしてしまうような迷惑なFutureに対しては、明示的にブロッキング動作をするものだと指定できるようです。

参考: multithreading - Asynchronous IO in Scala with futures - Stack Overflow

自作のテストコードでは、第二引数に何か与えると、Thread.sleepをblockingで囲んだコードが走ります。
% scala FutureBehaviorChecker.scala 10 1
Processors: 8
Thread active: 12
Current Thread: 1
Mode: sleep with blocking{}.
Finished: 04 [   33 ->   234] at  15
Finished: 06 [   33 ->   234] at  16
Finished: 07 [   34 ->   234] at  17
Finished: 01 [   33 ->   234] at  11
Finished: 09 [   35 ->   235] at  19
Finished: 05 [   33 ->   234] at  14
Finished: 00 [   29 ->   234] at  20
Finished: 08 [   35 ->   235] at  18
Finished: 02 [   33 ->   235] at  12
Finished: 03 [   33 ->   234] at  13

使用するスレッド数が増えてますね。長くなるのでわざわざここには貼りませんが、100個同時に走らせれば100スレッド生成されるようです。スレッドを立てるので使用するリソースは増えるものの、全体がブロックすることはなくなります。
この大量に生成されるスレッドは、Futureごとにスレッドをひとつ立てるのではなく、あくまでもスレッドプールに増えるものです。Future実行に使用したスレッドは一定時間ごとにじわじわとshutdownされていきます。Futureを使った後、定期的にスレッド数をprintしてみると、じわじわと減っていくのがわかります。


まとめ

  • Futureが走るスレッド数は有限
  • ブロックする操作 or 時間がかかる操作なら、とりあえずはblockingをつける
ブロッキング操作するFutureが多い場合は、java.util.concurrent.Executors関連調べて、もっとまじめに実行コンテキストの管理をした方が良いのではないかと思います。ブロックする操作の先にある何か(DBやネットワーク接続等)で扱える並列性よりもたくさん並べても、意味ないので…。
プロトタイプや遊びで組むぶんには、blockingに頼ってしまってもいいかな。

0 件のコメント:

コメントを投稿