'Hello, world!' in style (concurrently)

It’s another Daniel that started the trend of writing concurrent “Hello, world” programs.

The first thing I always do when playing around with a new software platform is to write a concurrent “Hello World” program. The program works as follows: One active entity (e.g. thread, Erlang process, Goroutine) has to print “Hello ” and another one “World!\n” with the two active entities synchronizing with each other so that the output always is “Hello World!\n”.

Then Dimitri Fontaine followed up and produced a Common Lisp implementation, leveraging the lparallel library. Ruby has a promising concurrency library based on the Actor model, called Celluloid, already used in projects like Sidekiq and Adhearsion. So let’s try doing a similar “Hello, world” exercise in Ruby/Celluloid.

We’ll start by defining a PORO (Plain Old Ruby Object), the Greeter.

class Greeter

   def initialize(msg)
     @msg = msg
   end

   def greet
     @msg
   end
 end

This will serve as the base class for two other classes, the Hello greeter and the World greeter. The Hello greeter will just extend Celluloid’s functionality.

class Hello < Greeter
  include Celluloid   
end

In addition to this, the World greeter will wait for messages asynchronously, append its own greeting upon reception, and print the assembled string to standard output.

class World < Greeter
  include Celluloid   

  def initialize(msg)
    super
    wait_for_hello!
  end

  def wait_for_hello
    loop do
      receive { |msg| puts "#{msg} #{greet}" }    
  end
end

When we now instantiate those classes, we obtain two objects running in concurrent threads.

hello = Hello.new "hello"
world = World.new "world"

The simplest form of making those threads synchronize their messages is to send the greeting of the Hello class to the mailbox of the World class.

100.times do
  world.mailbox << hello.greet
end

The above loop runs synchronously. But we can use futures, a concept from the Actor model, to run the loop in a non-blocking fashion. A future queues a computation, and allows you to retrieve the result later.

futures = []
100.times do
  futures << hello.future.greet
end
futures.each { |future| world.mailbox << future.value }

However, by design, calling value on a future is blocking. Tony Arcieri explains his motivations for the design of the system.

Celluloid::IO is one of many systems, including Erlang and Go, that demonstrate that “nonblocking” and “evented” I/O are orthogonal to callbacks. Celluloid uses Ruby’s coroutine mechanism to provide a synchronous I/O API on top of an underlying nonblocking system. However, where systems like Node force you to use nonblocking I/O for everything, Celluloid lets you mix and match blocking and nonblocking I/O as your needs demand.

There is also an alternate form of futures, namely block futures. In our “Hello, world” example, we would use them like so:

futures = []
100.times do
  futures << Celluloid::Future.new { world.mailbox << hello.greet }
end
futures.each { |future| future.value }

Celluloid provides additional abstractions to help managing actors. A supervisor, for example, monitors your actor and restarts if it crashes. You can declare actors and supervisors in so-called supervision groups. Applied to our example, we can write something like that:

class Runner
  include Celluloid

  def initialize
    @hello = Actor[:hello]
    @world = Actor[:world]
    run!
  end

 def run
   futures = []
   100.times do
     futures << @hello.future.greet
   end
     futures.each { |future| @world.mailbox << future.value }
 end
end

class MyGroup < Celluloid::SupervisionGroup
  supervise Hello, :as => :hello, :args => ["hello"]
  supervise World, :as => :world, :args => ["world"] 
  supervise Runner
end

MyGroup.run!

In the initialize method of Runner, we get references to our main actors by fetching them from the Actor registry. Additionally, the bang at the end of run reflects a syntax convention for asynchronous execution. Some people have been complaining about this (in Ruby, the bang is a convention to denote destructive or dangerous operation). The code in the run method is the same as in a previous example, but instead of running in the main thread, it now runs in its own, separate thread.

We have seen four different variations on the “Hello, world” exercice with Celluloid in the spirit of Daniel Himmelein’s original post. You can browse even more ideas on a gist. Obviously, there is more to Celluloid than this contrived example. A good place to read about the capabilities provided by the library is the wiki.

As far as the Actor model is concerned, it should be noted that it is an academic idea with forty years of history. It is a computational model grounded in mathematics and inspired by physics. As an abstraction, it lives on the same level as the Turing machine (which is said to be a special case of the Actor model). Framing this in the history of computer sciences, one needs to go back to the early 1960s, when hardware interrupts were beginning to emerge, and thus the first concurrency control problems. Theoretical debate ensued, opposing Edsger Dijkstra and Tony Hoare on the question of nondeterminism. In a Turing machine, nondeterminism is bound, and Edsger Dijkstra claimed it wasn’t possible to have a system with unbounded nondeterminism. But the opposite is what Carl Hewitt would argue in 1973 in a seminal paper titled A Universal Modular Actor Formalism for Artificial Intelligence, with a mathematical model stemming from domain theory backing up the claims.

“What’s at stake?” you might ask. Well, in a world characterized by multi-core machines and cloud-based computing, systems are inherently distributed and concurrent. These properties need to be dealt with in a conceptual framework that still guarantees delivery of service. The Actor model responds to the challenge with message passing in lieu of global state, a fundamental shift enabling the assertion that no matter how long a request might take, it will eventually be serviced.

If you are interested to delve deeper, you wouldn’t want to miss a video where you can watch and listen a lively debate around the core concepts of the Actor model with the master himself. And if you’re really studious, you’ll probably want to read his paper, Actor Model of Computation: Scalable Robust Information Systems.

Happy learning!

P.S. Follow me on Twitter.

P.S. Follow me on Twitter.

Daniel Szmulewicz 07 November 2012
blog comments powered by Disqus