Often, when it comes to parallel programming in Ruby, people make the
mistake of assuming that compound assignment operators like +=
are atomic — that is, that the value will be read, modified, and written
again without interference by other activity.
This is never guaranteed in JRuby, and it isn’t even normally guaranteed
by other Ruby implementations: modulo some small details, in Ruby
@foo += 1 is not fundamentally different from
@foo = @foo + 1 — a lot can happen while dispatching
that :+ method.
The only safe/portable way to do an atomic update in any Ruby up until
this point has been to use a Mutex:
@mutex.synchronize { @foo += 1 }
This avoids the “lost update” problem, among others.
In a Ruby implementation with a weaker memory model (JRuby and IronRuby,
currently), when multiple threads are accessing a variable, this is also
important to do in order to have a memory barrier that prevents CPU
instruction reordering or cache issues. Indeed, if you have multiple
threads using @foo, then you would need to use the mutex
even for simple accesses (since Ruby has no language provision for
Java-style volatile fields):
return @mutex.synchronize { @foo }
@mutex.synchronize { @foo = 3 }
This works (and performs better than you might expect), but for very
simple things it invites needless problems with lock contention. It’d
be nice to have portable volatile variables with
support for lockfree atomic update operations, wouldn’t it?
In response to this need, Charles Nutter (headius) and I came up with
the atomic gem, which provides
a simple class, Atomic, which embodies a volatile
reference and supports atomic updates to it.
It’s pretty primitive so far (and the fallback portable/generic
implementation still uses locks), but it provides the essentials in
a lockfree fashion under JRuby:
Atomic.new(initial_value=nil) – creates a new atomic reference
Atomic#value – returns the value
Atomic#value=(new_value) – sets the value
Atomic#swap(new_value) – sets the value and returns the old one
Atomic#update { |old_value| ... } – atomically sets the value to the result of the block
Atomic#try_update { |old_value| ... } – same thing, but fails rather than retrying on conflict
So, rather than this (which won’t actually work reliably in multiple threads under load):
@foo = 0
# ...
@foo += 1
You can do this:
@foo = Atomic.new(0)
# ...
@foo.update { |v| v + 1 }
The block passed to update doesn’t run exclusively; instead, the reference’s
value at the beginning and end of the block are compared to determine whether
it is safe to update (under JRuby, this uses a hardware test-and-set instruction
when available). If there was a change, then Atomic#update tries
the block again with the externally-changed value.
In other words, Atomic#update proceeds optimistically and retries
the block if there is a conflict. Because long-running expressions increase
the likelihood of conflict, the expression in the block should be simple, and
because it can sometimes be retried multiple times, it should ideally have no
side-effects.
Atomic#update is lockfree, but some specific use cases would
benefit strongly from actual hardware-level atomic operations, particularly
atomic increment and decrement. Since they cannot be performed on general
Ruby types, support for these operations will require a separate API (perhaps
a special subclass which holds only Fixnum values), but we’re
certainly interested in supporting such things in the long run.
Check out the atomic gem on github.