假设我需要将两个函数f: String => A
和g: A => B
应用于大型文本文件中的每一行,以最终创建B
列表。
由于文件很大且f
和g
很昂贵,我想让处理并发。我可以使用“并行集合”并执行io.Source.fromFile("data.txt").getLines.toList.par.map(l => g(f(l))
之类的操作,但它不会同时执行读取文件f
和g
。
在此示例中实现并发的最佳方法是什么?
答案 0 :(得分:12)
首先,重要提示:不要在.par
上使用List
,因为它需要复制所有数据(因为List
只能按顺序读取)。相反,请使用类似Vector
的内容,.par
转换可以在不进行复制的情况下进行。
看起来你正在以错误的方式思考并行性。这是将要发生的事情:
如果您有这样的文件:
0
1
2
3
4
5
6
7
8
9
功能f
和g
:
def f(line: String) = {
println("running f(%s)".format(line))
line.toInt
}
def g(n: Int) = {
println("running g(%d)".format(n))
n + 1
}
然后你可以这样做:
io.Source.fromFile("data.txt").getLines.toIndexedSeq[String].par.map(l => g(f(l)))
获得输出:
running f(3)
running f(0)
running f(5)
running f(2)
running f(6)
running f(1)
running g(2)
running f(4)
running f(7)
running g(4)
running g(1)
running g(6)
running g(3)
running g(5)
running g(0)
running g(7)
running f(9)
running f(8)
running g(9)
running g(8)
因此即使整个g(f(l))
操作发生在同一个线程上,您也可以看到每一行可以并行处理。因此,许多f
和g
操作可以在不同的线程上同时发生,但特定行的f
和g
将在顺序。
毕竟,这是你应该期待的方式,因为它实际上无法读取该行,运行f
并并行运行g
。例如,如果尚未读取该行,它如何在g
的输出上执行f
?
答案 1 :(得分:3)
您可以在map
上使用Future
:
val futures = io.Source.fromFile(fileName).getLines.map{ s => Future{ stringToA(s) }.map{ aToB } }.toIndexedSeq
val results = futures.map{ Await.result(_, 10 seconds) }
// alternatively:
val results = Await.result(Future.sequence(futures), 10 seconds)