Question Thread.join ne se comporte pas comme prévu dans scala


Dans le code ci-dessous, je crée 20 threads, les fait imprimer un message, veille et imprime un autre message. Je commence les threads dans mon thread principal, puis je rejoins également tous les threads. Je m'attendrais à ce que le message "tout soit terminé" ne soit imprimé qu'après que toutes les discussions se soient terminées. Pourtant, "tout est fait" est imprimé avant que tous les threads soient terminés. Quelqu'un peut-il m'aider à comprendre ce comportement?

Merci. Kent

Voici le code:

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

Voici la sortie:

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

16
2017-08-21 20:05


origine


Réponses:


Cela fonctionne si vous transformez le Range dans une 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")
  }

Le problème est que "1 to 5" est un Range, et les gammes ne sont pas "strictes", pour ainsi dire. En bon anglais, lorsque vous appelez la méthode map sur un Range, il ne calcule pas chaque valeur tout de suite. Au lieu de cela, il produit un objet - un RandomAccessSeq.Projection sur Scala 2.7 - qui a une référence à la fonction passée à mapper et un autre à la plage d'origine. Ainsi, lorsque vous utilisez un élément de la plage résultante, la fonction transmise à la carte est appliquée à l'élément correspondant de la plage d'origine. Et cela se produira chaque fois que vous accédez à un élément de la plage résultante.

Cela signifie que chaque fois que vous faites référence à un élément de tvous appelez new Thread() { ... } un nouveau. Comme vous le faites deux fois, et que la plage comporte 5 éléments, vous créez 10 threads. Vous commencez sur les 5 premiers et rejoignez les 5 autres.

Si cela prête à confusion, regardez l'exemple ci-dessous:

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)

Chaque fois que j'imprime t (en imprimant Scala REPL res4 et res5), l'expression obtenue est à nouveau évaluée. Cela arrive aussi pour les éléments individuels:

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

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

MODIFIER

À partir de Scala 2.8, Range sera strict, donc le code dans la question fonctionnera comme prévu initialement.


11
2017-08-21 21:34



Dans votre code, threads est différé - chaque fois que vous l'itérez, le for l'expression du générateur est à nouveau exécutée. Ainsi, vous créez réellement 10 threads là-bas - le premier foreach crée 5 et les lance, le second foreach crée 5 autres (qui ne sont pas démarrés) et les rejoint - car ils ne sont pas en cours d'exécution, join retourne immédiatement. Tu devrais utiliser toList sur le résultat de for faire un instantané stable.


7
2017-08-21 20:18