Ruby Threads

Let us have a quick look at Threads in Ruby. A thread of execution is a sequence of Ruby statements that run (or appear to run) in parallel with the main sequence of statements that the interpreter is running. Threads are implemented within the Ruby interpreter. That makes the Ruby threads completely portable - they don't rely on the operating system. At the same time, you don't get certain benefits from having native threads. This means that you may experience thread starvation (that's where a low-priority thread doesn't get a chance to run). If you manage to get your threads deadlocked, the whole process may grind to a halt. And if some thread happens to make a call to the operating system that takes a long time to complete, all threads will hang until the interpreter gets control back. Finally, if your machine has more than one processor, Ruby threads won't take advantage of that fact - because they run in one process, and in a single native thread, they are constrained to run on one processor at a time. Nevertheless Ruby threads are an efficient and lightweight way to achieve parallelism in your code.

Let us study the following code:

Here's the output:

New threads are created with the Thread.new call. It is given a block that contains the code to be run in a new thread. The newly created thread will exit when the block exits. The Kernel.sleep method is used to suspend the current thread for duration seconds (which may be any number, including a Float with fractional seconds). The method returns the actual number of seconds slept (rounded). Zero arguments causes sleep to sleep forever. When you create a thread, it can access any variables that are within scope at that point. However, any local variables that are then created within the thread are entirely local to that thread. You can pass any number of arguments into the block via Thread.new.

Why do we call join on each of the threads we created? When a Ruby program terminates, all threads are killed, regardless of their states. However, you can wait for a particular thread to finish by calling that thread's Thread.join method. The calling thread will block until the given thread is finished. By calling join on each of the requestor threads, you can make sure that all two requests have completed before you terminate the main program.

The Thread class has several class methods that serve various purposes. The Thread.main method returns a reference to the main thread, which spawns the others. The Thread.list method returns an array of Thread objects for all threads that are either runnable or stopped. The Thread.current method returns the currently executing thread.

The program so far:

The Thread.kill, Thread.pass, Thread.exit, Thread.start and Thread.stop methods are used to control the execution of threads (often from inside or outside). The Thread.kill method causes the given thread to exit. The Thread.pass method invokes the thread scheduler to pass execution to another thread. The Thread.exit method terminates the currently running thread and schedules another thread to be run. If this thread is already marked to be killed, Thread.exit returns the Thread. If this is the main thread, or the last thread, exit the process.

The complete program:

The Thread.start method is basically the same as Thread.new. The Thread.stop method stops execution of the current thread, putting it into a "sleep" state, and schedules execution of another thread.

The instance method thr.run wakes up thr, making it eligible for scheduling. The other instance method thr.terminate is a synonym for Thread.exit. Note that there is no instance method stop, so a thread can stop itself but not another thread.

An example of the need for thread exclusion involves an electronic banking application. Suppose one thread is processing a transfer of money from a savings account to a checking account (an account which allows the holder to write checks against deposited funds), and another thread is generating monthly reports to be sent out to customers. Without proper exclusion, the report-generation thread might read the customer's account data after funds had been been subtracted from savings but before they had been added to checking.

Mutex (short for "mutual exclusion") is a core class in Ruby 2 and is part of the standard library. It is common to use the synchronize method of the Mutex class and associate a block with it. synchronize locks the Mutex, runs the code in the block, and then unlocks the Mutex in an ensure clause so that exceptions are properly handled. Here is a simple model of our bank account example, using a Mutex object to synchronize thread access to shared account data.

Note: The Ruby Logo is Copyright (c) 2006, Yukihiro Matsumoto. I have made extensive references to information, related to Ruby, available in the public domain (wikis and the blogs, articles of various Ruby Gurus), my acknowledgment and thanks to all of them. Much of the material on rubylearning.github.io and in the course at rubylearning.org is drawn primarily from the Programming Ruby book, available from The Pragmatic Bookshelf.