Параллельная обработка файлов в Scala

Предположим, что мне нужно обрабатывать файлы в данной папке параллельно. В 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 ... )