Ruby & the Multicore CPU: Part Four - Current Concurrency Paradigms

Writing, debugging and maintaining safe multi-threaded code is notoriously difficult. Luckily, many useful gems and standard approaches have been created so that you don’t have to do all the heavy lifting yourself.

Queues and Jobs

A straightforward approach to maximising the use of resources that uses both additional processes and threads is through the use of background jobs and queues.

My favourite gem is sidekiq, but there are several good options.

Sidekiq allows you to create multi-threaded worker processes that will take jobs off a queue and process them independently of your main application process. You can set job priorities, it will automatically retry failed jobs and you can easily schedule jobs to be performed in the future. It also has excellent admin and monitoring functionality built in.

Server code:

require 'sidekiq'

class WorkerClass
  include Sidekiq::Worker

  def perform(argument: 42)
    hardmath = HardMathService.new(argument)
    result = hardmath.call
    puts "The answer is '#{result}'. Phew, that was hard."
  end
end

Client code:

MyWorker.perform_async(9000)

Jobs are queued into a redis database that can be accessed from multiple servers. This means its easy to distribute clients and workers over multiple CPUs, machines and failover devices.

Communicating Sequential Processes (CSP)

In 1978, Tony Hoare described a model for describing concurrent processes and their interaction a paper and subsequent book called Communicating Sequential Processes.1

The CSP model describes threads that communicate with each other through blocking channels (that is, a specific route of communication that requires the send and receiver to both be ready and waiting for the communication event to occur. This blocking communication process is called a rendezvous.

CSP has inspired several popular concurrent systems, including the goroutines and channels model used in the go programming language. The concurrent-ruby gem implements channels in its edge version.

Actor Model

The actor model is a fairly similarly concurrency model to CSP. Here, however, concurrent processes communicate with other processes (or themselves) via asynchronous mailboxes.

Since this model of communication is not as coordinated as in CSP, messages may be buffered, and in Elixir there is some very interesting recent work on backpressure as a feedback mechanism for coordinating processing between different parts of the system.

The Actor model in ruby was popularised in the celluloid gem, which seems to be out of maintenance now. It is also modelled in the edge version of the concurrent-ruby gem.

Reactor Model

The Reactor Model consists of a main event loop which dispatches incoming events to registered listener objects. These dispatched events are then processed in threads (from a thread pool) separate to the main event loop. The result of that processing may subsequently trigger other events and callbacks to again be dispatched in the event loop. The reactor model takes advantage of non-blocking IO in order to schedule threads and ensure that processing time is maximised.

Node.js uses the reactor pattern.

EventMachine and async both implement the reactor pattern.


  1. Introduction
    1. Concurrency vs. Parallelism
    2. Processes
    3. Threads
    4. Fibers
    5. Synchronicity
  2. Ruby up to 2.7
    1. MRI Ruby
    2. JRuby
    3. Rubinius
    4. TruffleRuby
  3. Processes and Threads

  4. Current Concurrency Paradigms
    1. Queues and Jobs
    2. Communicating Sequential Processes
    3. Actor Model
    4. Reactor Model
  5. Ruby 3.0 Concurrency and Parallelism
  1. There is a better version of the book here, but the site is down at the time of writing. It’s worth a check to see if its back up. 

Tagged: | ruby | concurrency | parallelism |