2009-08-21 19 views
16

W kodzie poniżej tworzę 20 wątków, każę im wydrukować wiadomość, przespać i wydrukować kolejną wiadomość. Uruchamiam wątki w głównym wątku, a następnie dołączam do wszystkich wątków. Oczekuję, że komunikat "wszystko gotowe" zostanie wydrukowany dopiero po zakończeniu wszystkich wątków. Jednak "wszystko gotowe" zostanie wydrukowane zanim wszystkie wątki zostaną wykonane. Czy ktoś może mi pomóc zrozumieć to zachowanie?Thread.join nie zachowuje się tak, jak się spodziewałem w scala

Dzięki. Kent

Oto kod:

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

Oto wynik:

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

Podtrzymuję tę kwestię, ponieważ specyficzna interakcja for-comprehensions, zakresów i wątków wydaje się być częstym powtarzającym się wzorem błędu. –

Odpowiedz

7

W kodzie threads jest odroczona - za każdym razem gdy go iteracyjne wyrażenie for generator jest uruchomiony nowo. W ten sposób tworzy się tam 10 wątków - pierwszy foreach tworzy 5 i uruchamia je, drugi tworzy kolejne 5 (które nie są uruchomione) i łączy je - ponieważ nie działają, join natychmiast wraca. Aby uzyskać stabilną migawkę, należy użyć wartości toList dla wyniku for.

+0

Dzięki, Pavel. To było dokładnie to. Nie wiem, jak usunąć powyższą niezupełnie odpowiedź. – Kent

11

To działa, jeśli przekształcenie Range w 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") 
    } 

Problem polega na tym, że "1 to 5" to Range i zakresy nie są "surowe", że tak powiem. W języku angielskim, po wywołaniu metody map na Range, nie oblicza ona każdej wartości w prawo. Zamiast tego tworzy obiekt - RandomAccessSeq.Projection na Scala 2.7 - który ma odniesienie do funkcji przekazanej do mapy, a innej do pierwotnego zakresu. Tak więc, gdy użyjesz elementu wynikowego zakresu, funkcja, którą przekazałeś do odwzorowania, zostanie zastosowana do odpowiedniego elementu oryginalnego zakresu. I to się stanie za każdym razem, gdy uzyskasz dostęp do dowolnego elementu wynikowego zakresu.

Oznacza to, że za każdym razem, gdy odwołujesz się do elementu t, ponownie wywołujesz new Thread() { ... }. Ponieważ robisz to dwa razy, a zakres ma 5 elementów, tworzysz 10 wątków. Zaczynasz na pierwszej 5, i dołącz na drugim 5.

Jeśli jest to mylące, spojrzeć na poniższy przykład:

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) 

każdym razem drukować t (poprzez Scala rEPL drukiem res4 i res5), wyrażone wyrażenie zostanie ocenione ponownie. Zdarza się dla poszczególnych elementów TOO:

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

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

EDIT

Jak Scala 2.8, Range będzie surowe, więc kod w pytaniu będzie działać jak pierwotnie oczekiwano.

Powiązane problemy