Ruby Threads


Threads are the Ruby implementation for a concurrent programming model. According to Wikipedia a 'thread' is defined as,

In computer science, a thread of execution is the smallest sequence of programmed instructions that can be managed independently by an operating system scheduler. A thread is a light-weight process.

Threads can be a challenging topic for beginners. Consider the following analogy. You arrive at your local bank to make a deposit and find there is only a single teller taking customers. There are four customers in front of you waiting to complete their transactions. The line moves slowly, one customer at a time. This is considered a single thread. The teller can only handle one customer at a time. Additional customers have to wait in a queue until the teller is free. From the customer's perspective, this is a poorly designed system. It would be much more efficient to have a second or third teller working simultaneously, serving multiple customers at the same time. Threads let your program process multiple tasks at the same time, increasing the overall efficiency.

As we have seen with other classes, the Thread class has a new constructor:

one = Thread.new do
         count = 0
         while(count < 10)
           puts "Thread One!"
           sleep 2
           count += 1
         end 
end

  two = Thread.new do
         count2 = 0
         while(count2 < 5)
           puts "Thread Two!"
           sleep 3
           count2 += 1
        end 
  end

  one.join()
  two.join()

## Results Below ##
Thread Two!
Thread One!
Thread One!
Thread Two!
Thread One!
Thread One!
Thread Two!
Thread One!
Thread Two!
Thread One!
Thread Two!
Thread One!
Thread One!
Thread One!
Thread One!
[Finished in 21.0s]

This simple example creates two instances of the Thread class, variables 'one' and 'two'. The sleep method acts as a "pause" button for our code. The number following sleep is the time, in seconds, we want the code to pause for. The example uses the two threads to allow us to do work in parallel. Notice the join methods at the bottom of the code? This joins our threads together, allowing our program to continue running until all threads our finished. For this reason, it takes 21 seconds for the code to finish. Without using join, the code would execute and finish immediately before printing anything to the screen. This is because once the main thread terminates, all other threads will terminate. The join method takes an optional parameter, a timeout, given in seconds. This is useful if we don't want our program to run for longer than 'x' amount of time.

Alternate Thread initialization

In order to create new threads, Ruby provides several methods including, start and fork.

Thread termination

The class method kill is meant to exit a given thread. Class methods are different than public instance methods that we've worked with before. As the name implies, they need to be invoked on the class rather than an instance of the class. For example:

thread = Thread.new { #code block goes here }
Thread.kill(thread) # pass in the thread to exit

Threads and Exceptions

If an exception occurs during a thread it will exit silently without stopping the program or showing an error. For debugging purposes, you may want to change this behavior so that the program will stop when an error is raised. To do this set the following flag on Thread to true:

Thread.abort_on_exception = true

The Real World

When is it possible and practical to utilize the Thread class? As it turns out, concurrency in Ruby is not the same as parallelism. We don't need to spawn threads to perform multiple tasks at the same time. The true value in threads comes from utilizing their ability to spawn a new process when there is some blocking I/O. This typically occurs when a program is waiting for a response. The response may be an HTTP request, database query, or file system operation. It's during these times where threads become advantages, allowing us to spawn a new process and perform other work.

Several popular open-source applications utilize threads. Again, they are mostly used to handle delays between request/response cycles. Puma, the most popular web server for Ruby, utilizes the Thread class exceptionally. Here is a snippet from the source code:

The '@spawned' instance variable on line 72 is incremented on the spawn_thread method call. Then on line 74 a new instance of the Thread class is created. This is one example of utilizing Ruby Threads to improve application performance.


One argument that arises often is that of concurrency vs. parallelism. The Ruby interpreter, called Matz's Ruby Interpreter (MRI) or CRuby, since it's written entirely in C, has a feature known as the Global Interpreter Lock (GIL). This feature is used internally by the interpreter to restrict multi-threading. The GIL is meant to protect against issues that arise from multi-threading. The increased performance comes at the cost of increased complexity. When Ruby was developed in the mid 90's multi-threading was more trouble than it was worth and so it made sense to prevent multi-threading. Today, however, it makes less sense. The silver lining is that languages continue to evolve and improve over time. Ruby 3.0 is focused on achieving "three times its speed" and may possibility remove GIL altogether. This would allow Ruby to run processes in parallel.

results matching ""

    No results matching ""