并行迭代文件中的行(Scala)?

时间:2011-07-19 17:34:06

标签: scala concurrency parallel-processing

我知道Scala中的并行集合。它们很方便!但是,我想迭代一个文件的行,这个文件对于并行的内存来说太大了。例如,我可以创建线程并在扫描器上设置锁定,但如果我可以运行以下代码,那将会非常棒:

Source.fromFile(path).getLines.par foreach { line =>

不幸的是,

error: value par is not a member of Iterator[String]

在这里实现一些并行性的最简单方法是什么?现在,我将阅读一些行并同时处理它们。

5 个答案:

答案 0 :(得分:31)

您可以使用分组轻松地将迭代器切片为可以加载到内存中的块,然后并行处理。

val chunkSize = 128 * 1024
val iterator = Source.fromFile(path).getLines.grouped(chunkSize)
iterator.foreach { lines => 
    lines.par.foreach { line => process(line) }
}

在我看来,这样的事情是最简单的方法。

答案 1 :(得分:10)

我会把它作为一个单独的答案,因为它与我的上一个根本不同(它实际上有效)

以下是使用演员的解决方案的大纲,这基本上是Kim Stebel的评论所描述的。有两个actor类,一个FileReader actor,可以根据需要从文件中读取各行,以及几个Worker actor。工作人员都向读取器发送行请求,并在从文件中读取行并行处理行。

我在这里使用Akka演员,但使用其他实现基本上是相同的想法。

case object LineRequest
case object BeginProcessing

class FileReader extends Actor {

  //reads a single line from the file or returns None if EOF
  def getLine:Option[String] = ...

  def receive = {
    case LineRequest => self.sender.foreach{_ ! getLine} //sender is an Option[ActorRef]
  }
}

class Worker(reader: ActorRef) extends Actor {

  def process(line:String) ...

  def receive = {
    case BeginProcessing => reader ! LineRequest
    case Some(line) => {
      process(line)
      reader ! LineRequest
    }
    case None => self.stop
  }
}

val reader = actorOf[FileReader].start    
val workers = Vector.fill(4)(actorOf(new Worker(reader)).start)
workers.foreach{_ ! BeginProcessing}
//wait for the workers to stop...

这样,一次不会有超过4个(或者你有多少工人)未经处理的行。

答案 2 :(得分:1)

以下帮助我实现

source.getLines.toStream.par.foreach( line => println(line))

答案 3 :(得分:0)

关于丹西蒙回答的评论让我思考。为什么我们不尝试在源中包装Source:

def src(source: Source) = Stream[String] = {
  if (source.hasNext) Stream.cons(source.takeWhile( _ != '\n' ).mkString)
  else Stream.empty
}

然后你可以像这样并行使用它:

src(Source.fromFile(path)).par foreach process

我尝试了这个,它无论如何编译并运行。我不确定它是否将整个文件加载到内存中,但我不会认为它是

答案 4 :(得分:0)

我意识到这是一个老问题,但您可能会发现iterata library中的ParIterator实现是一个有用的无需汇编的实现:

scala> import com.timgroup.iterata.ParIterator.Implicits._
scala> val it = (1 to 100000).toIterator.par().map(n => (n + 1, Thread.currentThread.getId))
scala> it.map(_._2).toSet.size
res2: Int = 8 // addition was distributed over 8 threads