2009-08-21 8 views
16

En el siguiente código creo 20 hilos, haga que cada uno imprima un mensaje, duerma e imprima otro mensaje. Comienzo los hilos en mi hilo principal y luego unir todos los hilos también. Esperaría que el mensaje de "todo listo" solo se imprima después de que todos los hilos hayan terminado. Sin embargo, "todo listo" se imprime antes de que todos los hilos terminen. ¿Alguien puede ayudarme a entender este comportamiento?Thread.join no se comporta como esperaba en scala

Gracias. Kent

Aquí está el código:

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

Aquí está la salida:

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

Estoy votando esta pregunta porque la interacción específica de for-comprehensions, ranges and threads parece ser un patrón de error recurrente común. –

Respuesta

7

En su código, threads se aplaza - cada vez que usted repite ella, la expresión for generador se ejecuta de nuevo. Por lo tanto, realmente crea 10 subprocesos allí: el primer foreach crea 5 y los inicia, el segundo foreach crea 5 más (que no se inician) y los une, ya que no se están ejecutando, join regresa inmediatamente. Debe usar toList en el resultado de for para hacer una instantánea estable.

+0

Gracias, Pavel. Eso fue exactamente eso. No sé cómo eliminar la respuesta no muy anterior. – Kent

11

funciona si transformar el Range en un 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") 
    } 

El problema es que "1 to 5" es una Range, y los rangos no son "estricta", por así decirlo. En buen inglés, cuando llama al método map en un Range, no calcula cada valor en ese momento. En su lugar, produce un objeto, un RandomAccessSeq.Projection en Scala 2.7, que tiene una referencia a la función pasada al mapa y otra al rango original. Por lo tanto, cuando usa un elemento del rango resultante, la función que pasó al mapa se aplica al elemento correspondiente del rango original. Y esto sucederá todas y cada una de las veces que acceda a cualquier elemento del rango resultante.

Esto significa que cada vez que se refiera a un elemento de t, está llamando al new Thread() { ... } de nuevo. Como lo hace dos veces, y el rango tiene 5 elementos, está creando 10 hilos. Se empieza en el primer 5, y unirse en el segundo 5.

Si esto es confuso, mira el siguiente ejemplo:

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) 

Cada vez que imprimo t (por tener Scala REPL impresión res4 y res5), la expresión producida se evalúa de nuevo. Sucede por elementos individuales también:

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

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

EDITAR

A partir de Scala 2.8, Range habrá estricta, por lo que el código en la pregunta va a funcionar como se espera.

Cuestiones relacionadas