2009-08-21 88 views
16

在下面的代碼中,我創建了20個線程,分別打印出消息,睡眠和打印另一條消息。我在我的主線程中啓動線程,然後加入所有線程。我希望在所有線程完成後才能打印「全部完成」消息。然而,在所有線程完成之前,「完成」都會被打印出來。有人能幫我理解這種行爲嗎?Thread.join在scala中沒有像我預期的那樣運行

謝謝。 肯特

下面是代碼:

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

我是upvoting這個問題,因爲for-comprehensions,範圍和線程的特定交互似乎是一個常見的經常性錯誤模式。 – 2009-08-21 21:47:56

回答

7

在你的代碼,threads被推遲 - 每次迭代它時,for發電機表達運行重新。因此,你實際上在那裏創建了10個線程 - 第一個foreach創建5並啓動它們,第二個foreach創建另外5個(它們未啓動)並加入它們 - 因爲它們沒有運行,所以join立即返回。對for的結果應該使用toList來製作穩定的快照。

+0

謝謝,帕維爾。這就是它。 我不知道如何刪除上面的不真實答案。 – Kent 2009-08-21 20:24:32

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") 
    } 

的問題是,「1 to 5」是Range,並且範圍不是「嚴」,可以這麼說。用英語說得很好,當你在Range上調用方法map時,它不會立即計算每個值。相反,它產生一個對象 - 一個在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(具有斯卡拉REPL打印res4res5 ),得到的表達式再次被評估。它發生於個別元素過多:

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

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

編輯

由於斯卡拉2.8的,Range將以嚴格的,所以在問題的代碼將作爲最初的預期。

相關問題