Thread.join的行为与我在scala中的预期不同

时间:2009-08-21 20:05:35

标签: scala join multithreading

在下面的代码中,我创建了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

2 个答案:

答案 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打印res4res5),都会再次评估所产生的表达式。它也适用于个别元素:

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来制作稳定的快照。