Предположим, что мне нужно обрабатывать файлы в данной папке параллельно. В Java я создал бы поток FolderReader
для чтения имен файлов из папки и пула потоков FileProcessor
. FolderReader
читает имена файлов и отправляет функцию обработки файлов (Runnable
) исполнителю пула.
В Scala я вижу два варианта:
- создать пул участников
FileProcessor
и запланировать функцию обработки файлов с помощью Actors.Scheduler
.
- создайте актера для каждого имени файла при чтении имен файлов.
Имеет ли смысл? Каков наилучший вариант?
Ответ 1
Я предлагаю всем своим энергиям держать все возможное в потоках. К счастью, у нас есть лучшие абстракции, которые заботятся о том, что происходит внизу, и в вашем случае мне кажется, что вам не нужно использовать актеров (пока вы можете), но вы можете использовать более простую абстракцию под названием Futures. Они являются частью открытой библиотеки Akka, и я думаю, что в будущем это будет частью стандартной библиотеки Scala.
Будущее [T] - это просто что-то, что вернет T в будущем.
Все, что вам нужно для запуска в будущем, - это иметь неявный ExecutionContext, который вы можете получить из службы-исполнителя java. Тогда вы сможете наслаждаться элегантным API и тем фактом, что будущее является монадой для преобразования коллекций в коллекции фьючерсов, сбора результата и так далее. Я предлагаю вам взглянуть на http://doc.akka.io/docs/akka/2.0.1/scala/futures.html
object TestingFutures {
implicit val executorService = Executors.newFixedThreadPool(20)
implicit val executorContext = ExecutionContext.fromExecutorService(executorService)
def testFutures(myList:List[String]):List[String]= {
val listOfFutures : Future[List[String]] = Future.traverse(myList){
aString => Future{
aString.reverse
}
}
val result:List[String] = Await.result(listOfFutures,1 minute)
result
}
}
Здесь многое происходит:
- Я использую
Future.traverse
, который получает в качестве первого параметра M[T]<:Traversable[T]
и как второй параметр a T => Future[T]
, или если вы предпочитаете Function1[T,Future[T]]
и возвращает Future [M [T]]
- Я использую метод
Future.apply
для создания анонимного класса типа Future[T]
Есть много других причин смотреть на фьючерсы Akka.
-
Фьючерсы могут отображаться, потому что они являются монадами, т.е. вы можете связать выполнение фьючерсов:
Future { 3 }.map { _ * 2 }.map { _.toString }
-
Фьючерсы имеют обратный вызов: future.onComplete, onSuccess, onFailure, andThen и т.д.
-
Фьючерсы поддерживают не только траверс, но и понимание.
Ответ 2
В зависимости от того, что вы делаете, это может быть так же просто, как
for(file<-files.par){
//process the file
}
Ответ 3
В идеале вы должны использовать двух актеров. Один для чтения списка файлов и один для фактического чтения файла.
Вы запускаете процесс, просто отправляя единственное сообщение "начать" первому актеру. Затем актер может прочитать список файлов и отправить сообщение второму актеру. Второй актер затем считывает файл и обрабатывает содержимое.
Наличие нескольких действующих лиц, которые могут казаться сложными, на самом деле является хорошей вещью в том смысле, что у вас есть куча объектов, общающихся с eachother, например, в теоретической системе OO.
Изменить: вы ДЕЙСТВИТЕЛЬНО не должны делать одновременное чтение одного файла.
Ответ 4
Я собирался написать именно то, что сделал @Edmondo1984, за исключением того, что он избил меня.:) Я второй его предложение в большой путь. Я также предлагаю вам прочитать документацию для Akka 2.0.2. Кроме того, я приведу несколько более конкретный пример:
import akka.dispatch.{ExecutionContext, Future, Await}
import akka.util.duration._
import java.util.concurrent.Executors
import java.io.File
val execService = Executors.newCachedThreadPool()
implicit val execContext = ExecutionContext.fromExecutorService(execService)
val tmp = new File("/tmp/")
val files = tmp.listFiles()
val workers = files.map { f =>
Future {
f.getAbsolutePath()
}
}.toSeq
val result = Future.sequence(workers)
result.onSuccess {
case filenames =>
filenames.foreach { fn =>
println(fn)
}
}
// Artificial just to make things work for the example
Thread.sleep(100)
execContext.shutdown()
Здесь я использую sequence
вместо traverse
, но разница будет зависеть от ваших потребностей.
Иди с Будущим, мой друг; Актер в этом случае является более болезненным подходом.
Ответ 5
Но если использовать актеров, что с этим не так?
Если нам нужно прочитать/записать в какой-либо файл свойств. Есть пример Java. Но все же с Аккой Актеры.
Допустим, у нас есть актер ActorFile
представляет один файл. Hm.. Вероятно, он не может представлять один файл. Правильно? (было бы неплохо, если бы). Таким образом, он представляет несколько файлов, таких как PropertyFilesActor
, затем:
Почему бы не использовать что-то вроде этого:
public class PropertyFilesActor extends UntypedActor {
Map<String, String> filesContent = new LinkedHashMap<String, String>();
{ // here we should use real files of cource
filesContent.put("file1.xml", "");
filesContent.put("file2.xml", "");
}
@Override
public void onReceive(Object message) throws Exception {
if (message instanceof WriteMessage) {
WriteMessage writeMessage = (WriteMessage) message;
String content = filesContent.get(writeMessage.fileName);
String newContent = content + writeMessage.stringToWrite;
filesContent.put(writeMessage.fileName, newContent);
}
else if (message instanceof ReadMessage) {
ReadMessage readMessage = (ReadMessage) message;
String currentContent = filesContent.get(readMessage.fileName);
// Send the current content back to the sender
getSender().tell(new ReadMessage(readMessage.fileName, currentContent), getSelf());
}
else unhandled(message);
}
}
... сообщение будет передаваться с параметром (fileName)
Он имеет свой собственный in-box
, принимающий такие сообщения, как:
- WriteLine (имя_файла, строка)
- ReadLine (имя_файла, строка)
Эти сообщения будут храниться в in-box
в порядке, один после antoher. Актер будет выполнять свою работу, получая сообщения из окна - хранение/чтение и, тем временем, отправку обратной связи sender ! message
назад.
Таким образом, скажем, если мы напишем файл свойств и отправим показ содержимого на веб-странице. Мы можем начать показ страницы (сразу после отправки сообщения для хранения данных в файл), и как только мы получим обратную связь, обновите часть страницы с помощью только что обновленного файла (с помощью ajax).
Ответ 6
Хорошо, возьмите свои файлы и вставьте их в параллельную структуру
scala> new java.io.File("/tmp").listFiles.par
res0: scala.collection.parallel.mutable.ParArray[java.io.File] = ParArray( ... )
Тогда...
scala> res0 map (_.length)
res1: scala.collection.parallel.mutable.ParArray[Long] = ParArray(4943, 1960, 4208, 103266, 363 ... )