Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread.join not behaving as I expected in scala

In the code below I create 20 threads, have them each print out a message, sleep, and print another message. I start the threads in my main thread and then join all of the threads as well. I would expect the "all done" message to only be printed after all of the threads have finished. Yet "all done" gets printed before all the threads are done. Can someone help me to understand this behavior?

Thanks. Kent

Here is the 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")
  }

Here is the output:

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
like image 655
Kent Avatar asked Aug 21 '09 20:08

Kent


People also ask

What happens if thread join is not called?

Yes, it is safe insofar as the thread will continue until completion by itself, you do not need to have a reference to the Thread object to keep it alive, nor will the behavior of the thread change in any way.

Does thread join wait?

lang. Thread class. The wait() is used for inter-thread communication while the join() is used for adding sequencing between multiple threads, one thread starts execution after first thread execution finished.

What is thread join () in threading?

Join() Blocks the calling thread until the thread represented by this instance terminates, while continuing to perform standard COM and SendMessage pumping. Join(Int32)

What happens when a thread tries to join itself?

If the current thread calls join(), the thread is attempting to join with itself. In other words, the current thread is waiting until it completes. You guessed it: The current thread waits forever! By the way, to obtain access to the current thread's Thread object, call Thread's currentThread() method.


1 Answers

It works if you transform the Range into a 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")
  }

The problem is that "1 to 5" is a Range, and ranges are not "strict", so to speak. In good English, when you call the method map on a Range, it does not compute each value right then. Instead, it produces an object -- a RandomAccessSeq.Projection on Scala 2.7 -- which has a reference to the function passed to map and another to the original range. Thus, when you use an element of the resulting range, the function you passed to map is applied to the corresponding element of the original range. And this will happen each and every time you access any element of the resulting range.

This means that each time you refer to an element of t, you are calling new Thread() { ... } anew. Since you do it twice, and the range has 5 elements, you are creating 10 threads. You start on the first 5, and join on the second 5.

If this is confusing, look at the example below:

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)

Each time I print t (by having Scala REPL print res4 and res5), the yielded expression gets evaluated again. It happens for individual elements too:

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

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

EDIT

As of Scala 2.8, Range will be strict, so the code in the question will work as originally expected.

like image 100
Daniel C. Sobral Avatar answered Nov 05 '22 23:11

Daniel C. Sobral