无法理解并行计算列表元素的正确方法,但在不计算元素(并行)时阻塞主线程。用例:我有一个URL链接列表和一个简单的html页面解析器,我想通过并行解析每个页面来减少从给定页面获取信息所需的时间,然后返回一个包含一些JSON数据的简单列表。
据我所知,我有两个选择:
与期货并行
我有一种在未来提取一些JSON数据的方法:
def extractData(link: String): Future[JValue] = // some implementation
我只是把它映射到一个链接列表上,类型是list[Foret[JValue]]:
val res: List[Future[JValue]] = listOfLink.map(extractData)
如果我调用sequence
(例如,从Scalaz或我自己的实现),它遍历这个列表并将其转换为Future[List[JValue]]
,那么链接仍将按顺序处理,但单独的线程不会给我任何效率,因为结果我需要获得List[JValue]
。
尝试使用ParSeq进行计算
在这个选项中,我有一个只提取数据的功能:
def extractData(link: String): JValue = // some implementation
但这次调用集合上的.par
:
val res: ParSeq[JValue] = listOfLinks.map(extractData)
但是在这种方式下,我不太明白如何在不按顺序解析每个链接的情况下,在不计算孔列表的情况下阻塞主线程
至于Akka,我不能在这里使用actor,所以只有Future
或Par*
在集合上映射extractData
时,链接将被并行处理。考虑一个稍微简化的例子:
import scala.concurrent._
import ExecutionContext.Implicits.global
def extractData(s: String) = future {
printf("Starting: %sn", s)
val i = s.toInt
printf("Done: %sn", s)
i
}
val xs = (0 to 5).map(_.toString).toList
val parsed = Future.sequence(xs map extractData)
现在,您将看到以下内容,这清楚地表明这些内容没有按顺序处理:
Starting: 0
Done: 0
Starting: 2
Done: 2
Starting: 1
Starting: 4
Done: 1
Starting: 3
Starting: 5
Done: 5
Done: 4
Done: 3
请注意,您可以使用Future.traverse
来避免创建期货的中间列表:
val parsed = Future.traverse(xs)(extractData)
在任何一种情况下,您都可以使用Await
:进行阻止
val res = Await.result(parsed, duration.Duration.Inf)
作为一个脚注:我不知道你是否计划使用Dispatch来执行HTTP请求,但如果没有,那就值得一看。它还提供了良好集成的JSON解析,文档中充满了如何使用期货的有用示例。