Подтвердить что ты не робот

Thread.join не работает, как я ожидал в scala

В приведенном ниже коде я создаю 20 потоков, каждый из них распечатывает сообщение, спящий и печатает другое сообщение. Я запускаю потоки в основном потоке, а затем присоединяюсь ко всем нитям. Я ожидаю, что сообщение "все сделано" будет напечатано только после завершения всех потоков. Тем не менее "все сделано" печатается до того, как все потоки будут выполнены. Может ли кто-нибудь помочь мне понять это поведение?

Спасибо. Kent

Вот код:

  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
4b9b3361

Ответ 1

Он работает, если вы преобразуете 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, он не вычисляет каждое значение справа. Вместо этого он создает объект - RandomAccessSeq.Projection на Scala 2.7 - который ссылается на функцию, переданную на карту, а другую на исходный диапазон. Таким образом, когда вы используете элемент результирующего диапазона, функция, которую вы передали на карту, применяется к соответствующему элементу исходного диапазона. И это будет происходить каждый раз при каждом доступе к любому элементу результирующего диапазона.

Это означает, что каждый раз, когда вы ссылаетесь на элемент 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 print res4 и res5), полученное выражение снова оценивается. Это происходит и для отдельных элементов:

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

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

ИЗМЕНИТЬ

В соответствии с Scala 2.8, Range будет строго, поэтому код в вопросе будет работать, как и ожидалось.

Ответ 2

В вашем коде threads отложено - каждый раз, когда вы его итерации, выражение генератора for запускается заново. Таким образом, вы фактически создаете 10 потоков там: первый foreach создает 5 и запускает их, второй foreach создает еще 5 (которые не запускаются) и соединяет их - поскольку они не работают, join немедленно возвращается, Вы должны использовать toList для результата for для создания стабильного моментального снимка.