Volatility: it's not just for sublimation any more

November 17, 2008 Adam Milligan

Multithreaded programming was a hot topic at RubyConf this year, and a common theme in many talks was the use of functional languages to prevent contention between threads. This totally makes sense to me, to the limited extent that I can wrap my head around truly functional programming, and I’m sure it’s an excellent approach. However, imagine a case in which we can’t just drop in a new language, so we need to write some multithreaded code in Ruby. I’m sure you won’t have to think too long or hard.

Now, one point several speakers at RubyConf made that I would like to reiterate is this: multithreaded programming is difficult. Gosh darn difficult. People who write software tend to thrive on determinism and linearity. After all, computers always do what we tell them to, right? They don’t make mistakes or change their minds; not like those silly, silly humans. But now big, bad concurrent programming comes along and suddenly computers can come up with different answers depending on, oh, the alignment of the planets. Chaos.

So, functional programming languages aside for the moment, what tools do we have to rein in the inevitable entropy that will, more than likely, eventually bring the planets into alignment against us?

Several years ago Andrei Alexandrescu wrote this excellent article on how to use the C++ type system (stick with me, it’s all Ruby after this paragraph) to automatically prevent race conditions at compile time. I recommend you read it; it’s quite short. Now, however you feel about static typing, you must admit that the approach he describes is a beautiful use of the expressiveness of the C++ type system. The question is, can we do something analogous in Ruby?

First off, lets start with some thread-unsafe code, similar to what Jim Weirich used in his talk on threads at RubyConf:

account = Account.new
threads = []

1000.times do |i|
  threads << Thread.new(account) do |account|
    account.credit(1)
  end
end

threads.each { |thread| thread.join }
puts account.balance

This code results in an account balance somewhere between 1 and 1,000. The exact value depends, of course, on the planets.

Now, to make this thread-safe. I came up with a few attempts using #alias_method and #undef_method, but didn’t find anything satisfying. After that, I figured I’d try a simple proxy to approximate the effect. Here’s a first cut:

class VolatileProxy
  attr_reader :obj

  def initialize(obj)
    @obj = obj
  end

  def method_missing(method, *args)
    raise "Unsynchronized message '#{method}' sent to volatile object"
  end
end

def volatile(obj)
  VolatileProxy.new(obj)
end

def locked_scope(volatile, mutex)
  mutex.synchronize do
    yield(volatile.obj)
  end
end

Now, calls to methods on objects you declare as volatile (shared across threads) will fail messily unless you use them within a locked_scope. To make this work the example code now becomes:

mutex = Mutex.new
account = volatile Account.new
threads = []

1000.times do |i|
  threads << Thread.new do
    locked_scope(account, mutex) { |safe_account| safe_account.credit(1) }
  end
end

threads.each { |thread| thread.join }
puts account.balance

This example has some issues, namely:

  • The final call to #balance will actually fail, since it’s not in a locked_scope; it would be nice to be able to declare individual functions as volatile without too verbose a syntax.
  • It’s tempting to name the locked account object the same name as the unlocked account object, but doing so will cause them to overwrite one another (fixed in Ruby 1.9, of course, but until then…)
  • The unlocked object is easily available. Given the opportunity to circumvent the lock, someone will do something horrible.

I really like the idea of using blocks to scope behavior like this. This particular example doesn’t feel particularly clean to me yet, but hopefully it will give some people something to think about. If you have a better approach, please don’t be afraid to shout it out.

About the Author

Biography

More Content by Adam Milligan
Previous
jQuery
jQuery

Yehuda Katz from EngineYard talks about using JavaScript development using jQuery.

Next
Pattern for Functional Testing
Pattern for Functional Testing

Regular Selenium tests (in Java) might look like: selenium.open("/login"); selenium.type("id=username", "b...

Enter curious. Exit smarter.

Register Now