Doing Two Things at Once with Threads

Problem

You want your program to run two or more pieces of code in parallel.

Solution

Create a new thread by passing a code block into THRead.new. That block will run simultaneously with any code you write after the call to THRead.new.

The following code features two competing threads. One continually decrements a variable by one, while the main programs thread busily incrementing the same variable by three. The decrementing thread starts its work earlier, but the incrementing thread always wins in the end, because it increments the counter by a larger number:

x = 0 Thread.new do while x < 5 x -= 1 puts "DEC: I decremented x to #{x} " end puts "DEC: x is too high; I give up! " end while x < 5 x += 3 puts "INC: I incremented x to #{x} " end # DEC: I decremented x to -1 # DEC: I decremented x to -2 # DEC: I decremented x to -3 # DEC: I decremented x to -4 # INC: I incremented x to -1 # DEC: I decremented x to -2 # INC: I incremented x to 1 # DEC: I decremented x to 0 # INC: I incremented x to 3 # DEC: I decremented x to 2 # INC: I incremented x to 5 # DEC: x is too high; I give up! x # => 5

Discussion

A Ruby process starts out running only one thread: the main thread. When you call Thread#new, Ruby spawns another thread and starts running it alongside the main thread. The operating system divides CPU time among all the running processes, and the Ruby interpreter further divides its alotted CPU time among all of its threads.

The block you pass into THRead.new is a closure (see Recipe 7.4), so it has access to all the variables that were in scope at the time you instantiated the thread. This means that threads can share variables; as a result, you don need complex communication schemes the way you do to communicate between processes. However, it also means that your threads can step on each others toes unless you e careful to synchronize any shared objects. In the example above, the threads were designed to step on each others toes, providing head-to-head competition, but usually you don want that.

Once a threads execution reaches the end of its code block, the thread dies. If your main thread reaches the end of its code block, the process will exit and all your other threads will die prematurely. If you want your main thread to stall and wait for some other thread to finish, you can call Thread#join on the thread in question.

This code spawns a subthread to count to one million. Without the call to Thread#join, the counter only gets up to a couple hundred thousand before the process exits:

#!/usr/bin/ruby -w # counter_thread.rb counter = 0 counter_thread = Thread.new do 1.upto(1000000) { counter += 1; } end counter_thread.join unless ARGV[0] puts "The counter was able to count up to #{counter}." $ ./counter_thread.rb The counter was able to count up to 1000000. $ ./counter_thread.rb dont_call_join The counter was able to count up to 172315.

You can get a list of the currently active thread objects with Thread.list:

Thread.new { sleep 10 } Thread.new { x = 0; 10000000.times { x += 1 } } Thread.new { sleep 100 } Thread.list # => [#, #, # #, #]

Here, the two running threads are the main irb thread and the thread running the counter loop. The two sleeping threads are the ones currently running sleep calls.

Категории