这里有SeqPar
对象,其中包含一个task
例程,该例程是一个简单的模拟Future
,它打印出一些调试信息并返回Future[Int]
类型。
问题是:为什么experiment1
可以并行运行,而experiment2
总是顺序运行?
object SeqPar {
def experiment1: Int = {
val f1 = task(1)
val f2 = task(2)
val f3 = task(3)
val computation = for {
r1 <- f1
r2 <- f2
r3 <- f3
} yield (r1 + r2 + r3)
Await.result(computation, Duration.Inf)
}
def experiment2: Int = {
val computation = for {
r1 <- task(1)
r2 <- task(2)
r3 <- task(3)
} yield (r1 + r2 + r3)
Await.result(computation, Duration.Inf)
}
def task(i: Int): Future[Int] = {
Future {
println(s"task=$i thread=${Thread.currentThread().getId} time=${System.currentTimeMillis()}")
i * i
}
}
}
当我运行experiment1
时,它会打印出来:
task=3 thread=24 time=1541326607613
task=1 thread=22 time=1541326607613
task=2 thread=21 time=1541326607613
experiment2
时:
task=1 thread=21 time=1541326610653
task=2 thread=20 time=1541326610653
task=3 thread=21 time=1541326610654
观察到差异的原因是什么?我确实知道for
的理解力像f1.flatMap(r1 => f2.flatMap(r2 => f3.map(r3 => r1 + r2 + r3)))
一样令人沮丧,但我仍然缺少一个要点:为什么一个人可以并行运行而另一个人不能并行运行。
答案 0 :(得分:3)
这是Future(…)
和flatMap
所做的结果:
val future = Future(task)
开始并行运行任务future.flatMap(result => task)
安排在task
完成时运行future
请注意,future.flatMap(result => task)
无法在future
完成之前开始并行运行任务,因为要运行task
,我们需要result
,仅在future
时可用完成。
现在让我们看看您的example1
:
def experiment1: Int = {
// construct three independent tasks and start running them
val f1 = task(1)
val f2 = task(2)
val f3 = task(3)
// construct one complicated task that is ...
val computation =
// ... waiting for f1 and then ...
f1.flatMap(r1 =>
// ... waiting for f2 and then ...
f2.flatMap(r2 =>
// ... waiting for f3 and then ...
f3.map(r3 =>
// ... adding some numbers.
r1 + r2 + r3)))
// now actually trigger all the waiting
Await.result(computation, Duration.Inf)
}
因此在example1
中,由于所有三个任务都花费相同的时间并同时开始,因此我们可能只需要在等待f1
时阻塞即可。当我们四处等待f2
时,其结果应该已经存在。
现在example2
有何不同?
def experiment2: Int = {
// construct one complicated task that is ...
val computation =
// ... starting task1 and then waiting for it and then ...
task(1).flatMap(r1 =>
// ... starting task2 and then waiting for it and then ...
task(2).flatMap(r2 =>
// ... starting task3 and then waiting for it and then ...
task(3).map(r3 =>
// ... adding some numbers.
r1 + r2 + r3)))
// now actually trigger all the waiting and the starting of tasks
Await.result(computation, Duration.Inf)
}
在此示例中,我们甚至没有在等待task(2)
完成之前构造task(1)
,因此任务无法并行运行。
因此,在使用Scala的Future
进行编程时,必须通过在example1
之类的代码和example2
之类的代码之间正确选择来控制并发性。或者,您可以研究可以更明确地控制并发性的库。
答案 1 :(得分:0)
这是因为Scala期货非常严格。创建Future之后,立即执行Future内部的操作,然后记忆其值。因此,您将失去参照透明性。在您的情况下,您的期货是在您的第一个任务调用中执行的,结果将被记录下来。它们不会在for中再次执行。在第二种情况下,将在您的期货中创建以进行理解,并且结果是正确的。