The thing I glossed over so far

The last few emails have been about thread-safety in Ruby. In all that text, I glossed over another important term: atomicity.
Today I'll show you what atomicity has to do with thread-safety, and how you can make critical parts of your code atomic. And most importantly: why the heck you might want to, or not. Let's get to it.

Read the rest of this email on the web.
I'll drag in an example from a previous email, a class that tracked inventory. The key method decremented a stock level.

  def decrease(item)
    @stock[item] -= 1

Previously, I stated that the decrease method wasn't thread-safe because it wasn't protecting a potentially concurrent write to the @stock Array. In other words, if two threads tried to update a value in the @stock Array at the same time, one of those updates might be lost. (Read on to learn more about why that happens). The solution was to wrap that interaction with a Mutex.

That fixed the issue, but it isn't the only issue with this line of code. Not only is the Array operation not protected, but the -= operator is not atomic, which can lead to data corruption. That's never a good thing. Remember, that means that you may end up with unexpected values in your variables, but your program continues to run as if everything were fine.

Today I'm throwing a new term in to the mix: atomicity.
So what's an atomic operation? And why the heck doesn't Ruby just make all the things atomic that need to be atomic, ya know, to make your life easier?
The word 'atomic' stems from 'atom'. Atoms are indivisible. Atomic operations in your code are indivisible as well, they can't be interrupted. When one thread is performing an atomic operation, other threads won't be able to interrupt it and perform the same operation.
Very few operations in Ruby are guaranteed to be atomic. Let's look at a concrete example: compound assignment operators. That's a fancy word for things like +=, -=, ||=, etc.
Note: the following code samples use my num_threads gem to cut down on the amount of boilerplate you have to look at. Check out the README if it's unclear how it works.
  require 'num_threads'

  x = 0
  10.threads do
    1000.times { x += 1 }
  # 10 threads are incrementing x 1000 times
  # the result should be 10,000
  puts x
Much like previous emails, I'll run the code against popular Ruby implementations.
MRI      : 10000
JRuby    : 1727
Rubinius : 7497
And as we can see, MRI produced the correct results, while the other implementations produced incorrect results. Why? All because the += operator in Ruby is not guaranteed to be atomic.
An operation like 
  x += 1 
will actually be expanded to something like the following at the VM level
  x = x + 1
At the VM level, the underlying implementation must
  1. get the current value of x.
  2. compute a new value by adding 1 to it.
  3. then replace the current value of x with this new value.
Let's play out the scenario here with one of the Ruby implementations that has no global lock, like JRuby or Rubinus. Here's a rough idea of the view from inside the VM.

One thread (Thread A) gets the current value of x. It's 0. It computes a new value. It's 1.
Then the kernel decides to pause Thread A to let another thread (Thread B) execute in its place. Thread B gets the current value of x. It's still 0. It computes a new value. It's 1. It replaces the current value of x with 1.
Thread B is finished, so Thread A gets another turn. It picks up where it left off and replaces the current value of x (now 1) with its computed value of x, also 1.
In this multi-threaded scenario, an update was lost. Two threads tried to increment a counter, but only one increment was actually recorded. Since the kernel controls the scheduling of threads, we can't guarantee that things will happen in any given order, unless we explicitly make it so. The above scenario might play out differently each time its executed, but will never be guaranteed to produce the correct result.

For this example program, this operation needs to be made atomic. The simplest way to do that is to wrap it in a Mutex, like you saw in a previous email. Here's how that can be done:

  require 'num_threads'
  x = 0
  mutex =

  10.threads do
    1000.times do
      mutex.synchronize { x += 1 }
  # 10 threads are incrementing x 1000 times
  # the result should be 10,000
  puts x
This code should be old news to you by now. It simply wraps the critical section of code that uses the += operator in a mutex. Now only one thread can occupy that critical code path at any given time.
All of the Ruby implementations display the correct result from this code.
This is the simplest way to fix this example code, making it atomic. However, acquiring and releasing a mutex has certain performance overhead every time you want to do it. In a hot code path, it could be a major problem.
Is there a better way? Of course.

But it doesn't ship with Ruby. Ruby only provides us with Mutex. But in other environments (C, JVM), there are actually low-level instructions that facilitate atomic operations. These can really help performance in hot code paths. This behaviour is nicely packaged up in the atomic rubygem. I'll just point you to the README for now, but I'll have more to say about it in my book.
So what does this mean for me and my code?

This is the most important question in the whole email.
All this learning about the foundations of multi-threaded programming is important, but this question always needs to be asked. When I tell you that the += operator is not atomic, does that mean you should rush to erase it from all your existing codebases? Wrap all uses of it in a local Mutex? Stop using it entirely?
Of course not.
What this does mean is that you need to be aware what context your code will be used in. I'll refer you again to the rule #2 of the 4 rules to safe concurrency
    2. If you must do it, don't share data across threads.
The reason that our code sample involving += failed so miserably is that we were sharing the counter variable across multiple threads, and multiple threads were incrementing it simultaneously.
In a real-world scenario, it's always preferable for each thread to have its own copy of a needed variable or object with any summing or joining of the data being handled by the main thread. In that case, we'd be obeying rule #2. We would use concurrency without sharing a variable across threads. Instead, each thread would have their own variable.
This is one of the keys to thread-safety, again, something you'll hear more about in my book.
I'll end by asking for a favour: hit reply and tell me how it's going. Do you feel like your understanding of multi-threaded programming is improving? Would you be more confident writing a multi-threaded script today than you were a few weeks ago?
Talk soon,

comments powered by Disqus