The dRuby Book

5.2 Understanding the Thread Class

When a Ruby interpreter executes a script, a main thread starts (see Figure 22, Main thread at start-up). The main thread is in charge of processing the main script. The main thread is invoked in the very beginning, and when the main thread ends, then the script also ends. The main thread is the longest-living thread within a script’s life cycle.

images/d2main_thread.png

Figure 22. Main thread at start-up

Ruby threading works by passing a block to the Thread class. You can pass parameters to the block by passing arguments in Thread.new.

  ​thread = Thread.new(1,2,3) do |x, y, z|​
  ​ # Operations to be threaded.​
  ​end​

This creates a “program flow” (see Figure 23, Launching the second thread).

images/d2thread1.png

Figure 23. Launching the second thread

A thread has various states, such as running or sleep. When the thread is waiting for IO or other threads, then it goes into sleep mode. A thread often goes back and forth between run and sleep mode and eventually finishes (see Figure 24, States of a thread).

images/d2thread3.png

Figure 24. States of a thread

When a thread ends, it contains the end state. If the thread was terminated by an exception, then it will raise an exception when you try to access the value.

You can check the state of a thread using the alive?, status, and value methods.

alive?

Returns true or false

status

Returns the following state code:

  • "run" running.

  • sleep sleeping.

  • aborting aborting.

  • false finished normally.

  • nil terminated by exception.

stop?

Returns true when the thread ended or is asleep.

value

Waits until the thread ends and returns the value. If the thread is already finished, then it returns the value immediately. If it was terminated by an exception, then it raises an exception. You can access value at any time, and an exception will be raised every time you access the value.

value waits and returns the value. If all you want is to wait until the end of the thread, then you can use the join method. When the join method is called, it blocks the calling thread until the receiver thread ends (see Figure 25, Threads waiting to join).

images/d2thread2.png

Figure 25. Threads waiting to join

To control the state of the thread, you can use the following methods:

exit

Terminate the thread.

wakeup

Change the thread in running mode.

run

Get the thread into running mode. Switch thread.

raise

Raise exception to the thread.

There are no methods to get other threads into sleep mode. If you want to get your own thread into sleep mode, then you can use the sleep or stop (a class method of the Thread class) method. If you use the sleep method, then you can stop the execution up to the specified time (or forever).

You can also investigate all threads within a process with the following class methods:

Thread.list

Lists all live threads

Thread.main

Returns the main thread

Thread.current

Returns the currently running thread

Let’s try this with irb.

When irb first starts, it should have only one thread as the main thread.

  ​% irb --prompt simple​
  ​>> Thread.list​
  ​=> [#<Thread:0x40.... run>]​
  ​>> Thread.list[0] == Thread.main​
  ​=> true​
  ​>> Thread.current == Thread.main​
  ​=> true​

Here is the code to generate numbers from 0 to 9. However, this uses sleep for each iteration.

  ​>> th = Thread.new { 10.times {|x| sleep; p [Thread.current, x]} }​
  ​=> #<Thread:0x... sleep>​

The thread of th is in sleep mode. The Thread.list, status, alive?, and stop? show you the statuses of each thread.

  ​>> Thread.list​
  ​=> [#<Thread:0x..... sleep>, #<Thread:0x.... run>]​
  ​>> th.status​
  ​=> "sleep"​
  ​>> th.alive?​
  ​=> true​
  ​>> th.stop?​
  ​=> true​

Let’s wake up th via the wakeup method.

  ​>> th.wakeup​
  ​=> #<Thread:0x2.... run>​
  ​[#<Thread:0x2.... run>, 0]>>​

th is now in running mode and prints out 0. Because both the main thread and the th thread print out strings, the screen output may not be indented properly. th should now be in sleep mode as it moves to the next iteration.

  ​>> th.status​
  ​=> "sleep"​

Let’s change the status of th into run mode. run changes the thread immediately, so the output styling may differ from when using wakeup.

  ​>> th.run​
  ​[#<Thread:0x2..... run>, 1]=> #<Thread:0x2..... run>​
  ​​
  ​>> th.run​
  ​[#<Thread:0x2..... run>, 2]=> #<Thread:0x2..... run>​
  ​​
  ​>> th.wakeup​
  ​=> #<Thread:0x2..... run>​
  ​[#<Thread:0x2..... run>, 3]​

Next, let’s terminate by raising an exception to th.

  ​>> th.raise('stop!')​
  ​=> nil​
  ​>> Thread.list​
  ​=> [#<Thread:0x..... run>]​
  ​>> th.status​
  ​=> nil​
  ​>> th.alive?​
  ​=> false​
  ​>> th.stop?​
  ​=> true​

Since it was terminated by an exception, th.status should return nil. Thread.list returns only a live thread, so it should return only the main thread. th is already terminated, alive? should return false, and stop? should return true.

How about th.value? It should raise the same exception as the one raised by th.raise(RuntimeError: stop!).

  ​>> th.value​
  ​RuntimeError: stop!​
  ​ from (irb):7​
  ​ from (irb):11:in `value'​
  ​ from (irb):11​

What if it terminated normally? This time, print out the numbers from 0 to 9 and end with ’complete’ immediately without sleep. Once the main thread is created, call th.join to wait for the end of the thread execution.

  ​>> th = Thread.new { 10.times { |x| p x} ; 'complete' }​
  ​>> th.join​
  ​0​
  ​1​
  ​2​
  ​3​
  ​4​
  ​5​
  ​6​
  ​7​
  ​8​
  ​9​
  ​=> #<Thread:0x..... dead>​
  ​>> th.status​
  ​=> false​
  ​>> th.alive?​
  ​=> false​
  ​>> th.stop?​
  ​=> true​

th.join usually waits for the thread to end. Since it already ended in this case, it returns immediately if you run th.join again.

  ​>> th.join​
  ​=> #<Thread:0x2ac4d35c dead>​

th.value should return ’complete’, which was evaluated by the thread right before it ended.

  ​>> th.value​
  ​=> "complete"​

This gives you basic control of threading; we’ve tried all the options in irb. In the next section, we’ll go through how to safely pass objects among threads.