在下面的代码中,我创建了20个线程,让它们分别打印出一条消息,睡眠并打印另一条消息。我在主线程中启动线程,然后加入所有线程。我希望只有在所有线程完成后打印“全部完成”消息。然而,在完成所有线程之前,“全部完成”被打印出来。有人可以帮我理解这种行为吗?
感谢。 肯特
以下是代码:
def ttest() = {
val threads =
for (i <- 1 to 5)
yield new Thread() {
override def run() {
println("going to sleep")
Thread.sleep(1000)
println("awake now")
}
}
threads.foreach(t => t.start())
threads.foreach(t => t.join())
println("all done")
}
这是输出:
going to sleep
all done
going to sleep
going to sleep
going to sleep
going to sleep
awake now
awake now
awake now
awake now
awake now
答案 0 :(得分:11)
如果您将Range
转换为List
:
def ttest() = {
val threads =
for (i <- 1 to 5 toList)
yield new Thread() {
override def run() {
println("going to sleep")
Thread.sleep(1000)
println("awake now")
}
}
threads.foreach(t => t.start())
threads.foreach(t => t.join())
println("all done")
}
问题是“1 to 5
”是Range
,范围不是“严格”,可以这么说。用英语说,当你在map
上调用方法Range
时,它不会立即计算每个值。相反,它会生成一个对象 - Scala 2.7上的RandomAccessSeq.Projection - 它引用了传递给map的函数和另一个传递给原始范围的函数。因此,当您使用结果范围的元素时,传递给map的函数将应用于原始范围的相应元素。每当您访问结果范围的任何元素时,都会发生这种情况。
这意味着每次引用t
元素时,您都会重新调用new Thread() { ... }
。由于您执行了两次,并且范围有5个元素,因此您创建了10个线程。你从前5开始,然后加入第二个5。
如果这令人困惑,请查看以下示例:
scala> object test {
| val t = for (i <- 1 to 5) yield { println("Called again! "+i); i }
| }
defined module test
scala> test.t
Called again! 1
Called again! 2
Called again! 3
Called again! 4
Called again! 5
res4: scala.collection.generic.VectorView[Int,Vector[_]] = RangeM(1, 2, 3, 4, 5)
scala> test.t
Called again! 1
Called again! 2
Called again! 3
Called again! 4
Called again! 5
res5: scala.collection.generic.VectorView[Int,Vector[_]] = RangeM(1, 2, 3, 4, 5)
每次我打印t
(通过让Scala REPL打印res4
和res5
),都会再次评估所产生的表达式。它也适用于个别元素:
scala> test.t(1)
Called again! 2
res6: Int = 2
scala> test.t(1)
Called again! 2
res7: Int = 2
修改强>
从Scala 2.8开始,Range
将是严格的,因此问题中的代码将按原先的预期运行。
答案 1 :(得分:8)
在您的代码中,threads
被延迟 - 每次迭代时,for
生成器表达式都会重新运行。因此,你实际上在那里创建了10个线程 - 第一个foreach
创建5并启动它们,第二个foreach
再创建5个(未启动)并加入它们 - 因为它们没有运行,{ {1}}立即返回。您应该在join
的结果上使用toList
来制作稳定的快照。