Building Concurrent Primitives in Ruby without a Queue

The number-one, easiest way to make Ruby threads communicate and synchronize is to use the built-in Queue class. You can even see this in the Ruby docs: This class provides a way to synchronize communication between threads.

Unfortunately, a Queue isn’t always what we want. So, how can we build our own primitives that are still nice and thread-safe?

The answer is comprised of three pieces:

  • Mutex: semaphore for coordinating access to shared data from multiple threads
  • ConditionVariable: used to suspend execution of a thread while waiting for a signal
  • Data: the data that needs to be protected

Let’s build a simple thread-safe mailbox. The basic pattern is: Always wrap your wait in a loop and the Mutex synchronize. We make sure to check our condition (in this case @data.nil?) before waiting to avoid the case where the cv has already been signaled.

require 'thread'
class Mailbox
  def initialize
    @data = nil
    @mutex = Mutex.new 
    @cv = ConditionVariable.new
  end

  def wait
    @mutex.synchronize do
      while @data.nil?
        @cv.wait(@mutex)
      end
      data = @data
      @data = nil
      data
    end
  end

  def signal(data)
    @mutex.synchronize do
      @data = data.clone
      @cv.signal
    end
  end
end

Here’s a quick example:

require 'thread'
initial_data = {payload: [4,5]}
mailbox = Mailbox.new

# consumer
t1 = Thread.new do
  d = mailbox.wait
  puts "GOT d: #{d}"
end

# producer
t2 = Thread.new do
  mailbox.signal "monkey pants #{i}"
end

t1.join
t2.join

puts "done"

Homework assignment for the reader: Use Mutex and ConditionVariable to implement your own ConcurrentQueue. For more information, dig into the POSIX docs.