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.
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).
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).
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
orfalse
- 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).
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.