web-dev-qa-db-ja.com

Scalaで複数のタスクを実行するにはどうすればよいですか?

50,000個のタスクがあり、それらを10個のスレッドで実行したい。 Java Executers.threadPool(10)を作成し、runnableを渡してから、すべてを処理するのを待つ必要があります。Scalaしかし、ドキュメントで解決策を見つけることができません。

41
yura

Scala 2.9.3以降

最も簡単なアプローチは、scala.concurrent.Futureクラスと関連インフラストラクチャを使用することです。 scala.concurrent.futureメソッドは、渡されたブロックを非同期的に評価し、非同期計算を表すFuture[A]をすぐに返します。先物は、マッピング、flatMapping、フィルタリング、エラーの回復などを含む、多くの非ブロッキング方法で操作できます。

たとえば、10個のタスクを作成するサンプルは次のとおりです。各タスクは任意の時間だけスリープし、渡された値の2乗を返します。

import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

val tasks: Seq[Future[Int]] = for (i <- 1 to 10) yield future {
  println("Executing task " + i)
  Thread.sleep(i * 1000L)
  i * i
}

val aggregated: Future[Seq[Int]] = Future.sequence(tasks)

val squares: Seq[Int] = Await.result(aggregated, 15.seconds)
println("Squares: " + squares)

この例では、最初に一連の個々の非同期タスクを作成し、完了したらintを提供します。次に、Future.sequenceを使用して、これらの非同期タスクを単一の非同期タスクに結合します。つまり、型のFutureSeqの位置を交換します。最後に、結果を待つ間、現在のスレッドを最大15秒間ブロックします。この例では、グローバル実行コンテキストを使用します。このコンテキストは、fork/joinスレッドプールによってサポートされています。自明ではない例については、おそらくアプリケーション固有のExecutionContextを使用する必要があります。

一般に、ブロッキングは可能な限り回避する必要があります。 FutureonSuccessonFailureなど、非同期スタイルでのプログラミングに役立つ他のコンビネーターがonCompleteクラスで使用できます。

また、 Akka ライブラリの調査を検討してください。このライブラリは、ScalaおよびJava、およびscala.concurrentと相互運用するアクターベースの並行性を提供します。

Scala 2.9.2およびそれ以前

この最も簡単なアプローチは、ActorsフレームワークのサブコンポーネントであるScalaのFutureクラスを使用することです。 scala.actors.Futures.futureメソッドは、渡されたブロックのFutureを作成します。その後、scala.actors.Futures.awaitAllを使用して、すべてのタスクが完了するまで待機できます。

たとえば、10個のタスクを作成するサンプルは次のとおりです。各タスクは任意の時間だけスリープし、渡された値の2乗を返します。

import scala.actors.Futures._

val tasks = for (i <- 1 to 10) yield future {
  println("Executing task " + i)
  Thread.sleep(i * 1000L)
  i * i
}

val squares = awaitAll(20000L, tasks: _*)
println("Squares: " + squares)
58
mpilquist

Scala actors libraryまたはAkka。

したがって、タスクの処理方法を知っているアクターのプールを作成する必要があるようです。アクターは基本的に、Akkaチュートリアル( http://doc.akkasource.org/tutorial-chat-server-scala )のreceiveメソッドを持つ任意のクラスになります:

class MyActor extends Actor {
  def receive = {
    case "test" => println("received test")
    case _ =>      println("received unknown message")
 }}

val myActor = Actor.actorOf[MyActor]
myActor.start

アクターインスタンスのプールを作成し、タスクをメッセージとして実行する必要があります。 Akkaアクタープーリングに関する役立つ投稿を次に示します。 http://vasilrem.com/blog/software-development/flexible-load-balancing-with-akka-in-scala/

あなたの場合、タスクごとに1つのアクターが適切な場合があります(アクターはスレッドと比較して非常に軽量であるため、単一のVMでそれらのLOTを使用できます)か、より高度な負荷分散が必要になる場合があります。

編集:上記のサンプルアクターを使用して、メッセージを送信するのは次のように簡単です。

myActor ! "test"

次に、アクターは「受信したテスト」を標準出力に出力します。

メッセージはどのようなタイプでもかまいません。Scalaのパターンマッチングと組み合わせると、柔軟な同時アプリケーションを構築するための強力なパターンが得られます。

一般に、Akkaアクターはスレッド共有の観点から「正しいことをする」でしょう。OPのニーズのために、デフォルトは問題ないと思います。ただし、必要に応じて、アクターが使用するディスパッチャをいくつかのタイプのいずれかに設定できます。

* Thread-based
* Event-based
* Work-stealing
* HawtDispatch-based event-driven

アクターにディスパッチャを設定するのは簡単です:

class MyActor extends Actor {
  self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher("thread-pool-dispatch")
    .withNewThreadPoolWithBoundedBlockingQueue(100)
    .setCorePoolSize(10)
    .setMaxPoolSize(10)
    .setKeepAliveTimeInMillis(10000)
    .build
}

http://doc.akkasource.org/dispatchers-scala を参照してください

この方法で、スレッドプールのサイズを制限できますが、再び、元のユースケースは、おそらくデフォルトのディスパッチャーを使用する50K Akkaアクターインスタンスで満足でき、うまく並列化できます。

これは、実際にAkkaができることのほんの一部に過ぎません。 ErlangがScala言語に提供するものの多くをもたらします。アクターは他のアクターをモニターして再起動し、自己修復アプリケーションを作成できます。Akkaはソフトウェアトランザクションメモリおよび他の多くの機能も提供します。 Scalaの「キラーアプリ」または「キラーフレームワーク」。

16
Janx

Mpilquistの応答に似ていますが、非推奨のAPIがなく、カスタムExecutionContextを介したスレッド設定を含む別の回答があります。

import Java.util.concurrent.Executors
import scala.concurrent.{ExecutionContext, Await, Future}
import scala.concurrent.duration._

val numJobs = 50000
var numThreads = 10

// customize the execution context to use the specified number of threads
implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(numThreads))


// define the tasks
val tasks = for (i <- 1 to numJobs) yield Future {
  // do something more fancy here
  i
}

// aggregate and wait for final result
val aggregated = Future.sequence(tasks)
val oneToNSum = Await.result(aggregated, 15.seconds).sum
8
Holger Brandl

「10個のスレッドで実行する」場合は、スレッドを使用します。 Scalaのアクターモデル。これは通常、Scalaは並行性に適している、hidesなど)あなたがそれらを表示しないように詳細。

アクターを使用するか、先物を使用して単純な計算を行うだけで、それらを50000個作成して実行できます。問題に直面するかもしれませんが、それらは異なる性質のものです。

8