views:

172

answers:

2

This article states that double-checked locking is unsafe on certain language/hardware combinations when a shared variable can be updated with a reference to an object that is only partially initialized.

I was wondering: does this also apply to Ruby? Is this something that varies by the Ruby implementation on the platform that it is run on or is the correct behavior detailed in the language specification?

+2  A: 

Which implementation of Ruby? Ruby 1.8, 1.9, and JRuby have quite different threading implementations so it may be impossible to say.

I don't have the answer for your question, but it appears that you might be trying to write fast concurrent code in Ruby. My experiences with Ruby 1.8 is that this isn't a reasonable goal. Ruby 1.9 may be better, but still has a global interpreter lock like Python.

If you are writing Ruby code where you are considering these sort of potentially unsafe optimizations to wring out some extra performance, you should probably consider using another language. Idiomatic Ruby tends to emphasize readability and expressiveness before efficiency and speed. Trying to wring speed increases and increased reliability out of the MRI has been an exercise in frustration for me.

I've been working on a Ruby project for awhile that has just entered the phase where we are seriously looking at optimization. Running the project in JRuby and replacing the bottlenecks implemented in Ruby with Java libraries resulted in a pretty remarkable increase in speed and reliability with relatively little effort on our part.

JRuby is not perfect, but its Java integration has proven to be helpful and easy. If it is impossible to use JRuby for your project, or you are comfortable with C but not Java, and sure you can write safe C without memory leaks, writing a C extensions to Ruby 1.8 or 1.9 may be the best path.

I apologize if this question was completely academic in nature. Perhaps if you could let us know what, exactly, you're trying to do?

stephenjudkins
This is almost "completely academic" in nature. I recalled seeing some lazy-initialization of objects shared by multiple threads in some code a while back and asked this question so I could be better informed if I ever come across something like that in the future.
Readonly
That's a good point about the GIL. If only one thread can run at once, I would presume that the GIL would be used to ensure that an object's instantiation and initialization occur as one atomic action. Not sure if this is true, but it sounds like the reasonable design decision.
Readonly
+2  A: 

Double checking in Java is dangerous because a value might be assigned to the variable before the constructor is actually finished. This doesn't happen in Ruby: constructing an object is the same as assigning the result of a function call to a variable, because constructors are just normal functions. The assignment will only happen after the constructor has completely finished.

On MRI, an assignment is atomic. There is no official Ruby language or memory model specification but alternative implementations tend to try hard to mimic MRI's behavior even down to the most obscure details. Atomicity of assignment statement is a fairly important behavior so I expect that assignments on alternative Ruby implementations is atomic as well.

If you don't mind accidentally constructing two objects during contention, with one object being garbage collected later because it's duplicate, then you can use the ||= operator, like this:

@singleton ||= FooBar.new

||= atomically assigns the result of the given expression to the variable, but only if the variable wasn't already set. You can end up with two FooBar.new instances but only one will be reachable. Ruby on Rails uses this technique internally to avoid using mutexes in some places.

Hongli