У меня 50 000 задач и вы хотите выполнить их с помощью 10 потоков. В Java я должен создать Executers.threadPool(10) и передать runnable, а затем ждать, чтобы обработать все. Scala, как я понимаю, особенно полезен для этой задачи, но я не могу найти решение в документах.
Как выполнить несколько задач в Scala?
Ответ 1
Scala 2.9.3 и более поздние
Самый простой подход - использовать класс scala.concurrent.Future
и связанную с ним инфраструктуру. Метод scala.concurrent.Future
асинхронно оценивает переданный ему блок и сразу возвращает a Future[A]
, представляющий асинхронное вычисление. Фьючерсы можно манипулировать множеством неблокирующих способов, включая отображение, flatMapping, фильтрацию, восстановление ошибок и т.д.
Например, здесь образец, который создает 10 задач, где каждая задача сбрасывает произвольное количество времени, а затем возвращает квадрат переданного ему значения.
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
для объединения этих задач async в одну задачу async - замену позиции Future
и Seq
в типе. Наконец, мы блокируем текущий поток до 15 секунд, ожидая результата. В этом примере мы используем глобальный контекст выполнения, который поддерживается пулом потоков fork/join. Для нетривиальных примеров вы, вероятно, захотите использовать специальное приложение ExecutionContext
.
Как правило, следует избегать блокировки, когда это возможно. В классе Future
есть другие комбайнаторы, которые могут помочь программе в асинхронном стиле, включая onSuccess
, onFailure
и onComplete
.
Кроме того, рассмотрим библиотеку Akka, которая обеспечивает concurrency для Scala и Java на основе актера и взаимодействует с scala.concurrent
.
Scala 2.9.2 и ранее
Этот простейший подход заключается в использовании класса Scala Future, который является подкомпонентом структуры Actors. Метод scala.actors.Futures.future создает будущее для переданного ему блока. Затем вы можете использовать scala.actors.Futures.await, чтобы дождаться завершения всех задач.
Например, здесь образец, который создает 10 задач, где каждая задача сбрасывает произвольное количество времени, а затем возвращает квадрат переданного ему значения.
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)
Ответ 2
Вы хотите посмотреть либо библиотеку актеров Scala, либо Akka. Акка имеет более чистый синтаксис, но либо сделает трюк.
Итак, похоже, вам нужно создать пул актеров, которые знают, как обрабатывать ваши задачи. Актер в основном может быть любым классом с методом приема - из учебника 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
Вам нужно создать пул экземпляров актеров и запустить свои задачи в виде сообщений. Вот сообщение об активном объединении Акка, которое может быть полезно: http://vasilrem.com/blog/software-development/flexible-load-balancing-with-akka-in-scala/
В вашем случае может быть уместен один актер в каждой задаче (актеры очень легки по сравнению с потоками, поэтому вы можете иметь много из них в одной виртуальной машине), или вам может потребоваться более сложная балансировка нагрузки между ними.
EDIT: Используя вышеприведенный пример, отправку его сообщения так же просто:
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 с использованием диспетчеров по умолчанию и он будет хорошо распараллелить.
Это действительно только царапины поверхности того, что может сделать Акка. Это приносит много того, что предлагает Erlang для языка Scala. Актеры могут контролировать других участников и перезапускать их, создавая приложения для самовосстановления. Akka также обеспечивает программную транзакционную память и многие другие функции. Это, возможно, "приложение-убийца" или "оболочка-убийца" для Scala.
Ответ 3
Если вы хотите "выполнить их с помощью 10 потоков", используйте потоки. Scala актер-модель, о которой обычно говорят люди, когда они говорят, что Scala хороша для concurrency, скрывает такие детали, чтобы вы их не видели.
Использование актеров или фьючерсов со всеми, что у вас есть, это простые вычисления, вы просто создали бы 50000 из них и позволили бы им работать. Вы можете столкнуться с проблемами, но они носят иной характер.
Ответ 4
Вот еще один ответ, похожий на ответ 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