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

Ken*_*ent 17 multithreading scala join

在下面的代码中,我创建了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")
  }
Run Code Online (Sandbox Code Playgroud)

这是输出:

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
Run Code Online (Sandbox Code Playgroud)

Dan*_*ral 11

如果你转换RangeList:

  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")
  }
Run Code Online (Sandbox Code Playgroud)

问题是" 1 to 5"是一个Range,范围不是"严格",可以这么说.用英语说,当你map在a上调用方法时Range,它不会计算每个值.相反,它会生成一个对象 - Scala 2.7上的RandomAccessSeq.Projection - 它引用了传递给map的函数和另一个传递给原始范围的函数.因此,当您使用结果范围的元素时,传递给map的函数将应用于原始范围的相应元素.每次访问结果范围的任何元素时,都会发生这种情况.

这意味着每次你引用一个元素时t,你都会new Thread() { ... }重新调用.由于您执行了两次,并且范围有5个元素,因此您创建了10个线程.你从前5开始,然后加入第2 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)
Run Code Online (Sandbox Code Playgroud)

每次我打印t(通过Scala REPL打印res4res5),都会再次评估所产生的表达式.它也适用于个别元素:

scala> test.t(1)
Called again! 2
res6: Int = 2

scala> test.t(1)
Called again! 2
res7: Int = 2
Run Code Online (Sandbox Code Playgroud)

编辑

从Scala 2.8开始,Range将是严格的,因此问题中的代码将按原先的预期工作.


Pav*_*aev 8

在您的代码中,threads延迟 - 每次迭代时,for生成器表达式都会重新运行.因此,你实际上在那里创建了10个线程 - 第一个foreach创建5并启动它们,第二个foreach创建5个(未启动)并加入它们 - 因为它们没有运行,join立即返回.您应该使用toList结果for来生成稳定的快照.