Thread.join не ведет себя так, как я ожидал в Scala - PullRequest
17 голосов
/ 22 августа 2009

В приведенном ниже коде я создаю 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

Ответы [ 2 ]

11 голосов
/ 22 августа 2009

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

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

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

EDIT

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

8 голосов
/ 22 августа 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...