web-dev-qa-db-fra.com

Comment exécuter plusieurs tâches dans Scala?

J'ai 50 000 tâches et je veux les exécuter avec 10 threads. Dans Java je devrais créer Executers.threadPool (10) et passer runnable à est ensuite attendre de tout traiter. Scala si je comprends bien utile pour cette tâche, mais je ne trouve pas de solution dans les documents.

41
yura

Scala 2.9.3 et versions ultérieures

L'approche la plus simple consiste à utiliser le scala.concurrent.Future classe et infrastructure associée. Le scala.concurrent.future la méthode évalue de manière asynchrone le bloc qui lui est transmis et renvoie immédiatement un Future[A] représentant le calcul asynchrone. Les contrats à terme peuvent être manipulés de plusieurs manières non bloquantes, y compris le mappage, le flatMapping, le filtrage, la récupération des erreurs, etc.

Par exemple, voici un exemple qui crée 10 tâches, où chaque tâche dort un temps arbitraire, puis retourne le carré de la valeur qui lui est transmise.

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)

Dans cet exemple, nous créons d'abord une séquence de tâches asynchrones individuelles qui, une fois terminées, fournissent un int. Nous utilisons ensuite Future.sequence pour combiner ces tâches asynchrones en une seule tâche asynchrone - en échangeant la position du Future et du Seq dans le type. Enfin, nous bloquons le thread actuel pendant 15 secondes maximum en attendant le résultat. Dans l'exemple, nous utilisons le contexte d'exécution global, qui est soutenu par un pool de threads fork/join. Pour des exemples non triviaux, vous voudrez probablement utiliser une application spécifique ExecutionContext.

Généralement, le blocage doit être évité autant que possible. Il existe d'autres combinateurs disponibles sur la classe Future qui peuvent aider à programmer dans un style asynchrone, notamment onSuccess, onFailure et onComplete.

Envisagez également d'étudier la bibliothèque Akka , qui fournit une concurrence basée sur les acteurs pour Scala et Java, et interagit avec scala.concurrent.

Scala 2.9.2 et versions antérieures

Cette approche la plus simple consiste à utiliser la classe Future de Scala, qui est une sous-composante du cadre Actors. La méthode scala.actors.Futures.future crée un avenir pour le bloc qui lui est transmis. Vous pouvez ensuite utiliser scala.actors.Futures.awaitAll pour attendre la fin de toutes les tâches.

Par exemple, voici un exemple qui crée 10 tâches, où chaque tâche dort un temps arbitraire, puis retourne le carré de la valeur qui lui est transmise.

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

Vous voulez regarder la bibliothèque d'acteurs Scala ou Akka. Akka a une syntaxe plus propre, mais l'une ou l'autre fera l'affaire.

Il semble donc que vous deviez créer un pool d'acteurs qui savent comment traiter vos tâches. Un acteur peut être essentiellement n'importe quelle classe avec une méthode de réception - du didacticiel Akka ( http://doc.akkasource.org/tutorial-chat-server-scala ):

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

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

Vous souhaiterez créer un pool d'instances d'acteurs et leur envoyer vos tâches sous forme de messages. Voici un article sur la mise en commun des acteurs Akka qui peut être utile: http://vasilrem.com/blog/software-development/flexible-load-balancing-with-akka-in-scala/

Dans votre cas, un acteur par tâche peut être approprié (les acteurs sont extrêmement légers par rapport aux threads, vous pouvez donc en avoir BEAUCOUP dans une seule machine virtuelle), ou vous pourriez avoir besoin d'un équilibrage de charge plus sophistiqué entre eux.

EDIT: En utilisant l'exemple d'acteur ci-dessus, lui envoyer un message est aussi simple que cela:

myActor ! "test"

L'acteur affichera alors le "test reçu" sur la sortie standard.

Les messages peuvent être de tout type et, lorsqu'ils sont combinés avec la correspondance de modèles de Scala, vous disposez d'un modèle puissant pour créer des applications simultanées flexibles.

En général, les acteurs d'Akka "feront la bonne chose" en termes de partage de threads, et pour les besoins du PO, j'imagine que les valeurs par défaut sont correctes. Mais si vous en avez besoin, vous pouvez définir le répartiteur que l'acteur doit utiliser sur l'un des types suivants:

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

Il est trivial de définir un répartiteur pour un acteur:

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

Voir http://doc.akkasource.org/dispatchers-scala

De cette façon, vous pourriez limiter la taille du pool de threads, mais encore une fois, le cas d'utilisation d'origine pourrait probablement être satisfait avec des instances d'acteurs Akka de 50 Ko utilisant des répartiteurs par défaut et il se paralléliserait bien.

Cela ne fait qu'effleurer la surface de ce qu'Akka peut faire. Il apporte beaucoup de ce qu'Erlang offre au langage Scala. Les acteurs peuvent surveiller d'autres acteurs et les redémarrer, créant des applications d'auto-réparation. Akka fournit également une mémoire transactionnelle logicielle et de nombreuses autres fonctionnalités. C'est sans doute la "killer app" ou "killer framework" pour Scala.

16
Janx

Voici une autre réponse similaire à la réponse de mpilquist mais sans API obsolète et incluant les paramètres de thread via un ExecutionContext personnalisé:

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

Si vous souhaitez "les exécuter avec 10 threads", utilisez des threads. Le modèle d'acteur de Scala, qui est généralement ce dont les gens parlent quand ils disent Scala est bon pour la concurrence, masque ces détails pour que vous ne les voyiez pas.

Utiliser des acteurs, ou des futurs avec tout ce que vous avez, sont de simples calculs, il vous suffit d'en créer 50000 et de les laisser s'exécuter. Vous pourriez être confronté à des problèmes, mais ils sont de nature différente.

8
Daniel C. Sobral