Synchronized attr_accessor

MenTaLguY wrote:

@lock.synchronize do
that each thread has a consistent view of memory. For instance, when
reading the instance variable @obj outside the protection of the
lock, it is possible that a thread could @obj being non-null, but not
seeing the effects of initializing the object until much later!

(…and later restated)

For instance, if reading the instance variable @obj outside the
protection of the lock, it is possible for a thread to see that @obj
is non-null long before the effects of the object’s initialize method
have become visible to it!

So you’re saying that, as the line

     @obj = Whatever.new

is executed, it is possible (in some ruby implementations, but not MRI
1.8) that @obj will be assigned the Whatever instance before the #new
call completes? (That does seem to be what the wikipedia entry is
warning about.[1])

Scary! Your point that we should rely on the locking primitives for all
access (and hope that they get the memory barrier right) is well taken.
Which means always paying the sync cost:

def obj
@lock.synchronize do
@obj ||= Whatever.new
end
end

I wonder if the following will be a more efficient alternative, or worse
because of the singleton method:

def obj
@lock.synchronize do
@obj = Whatever.new
def self.obj
@obj
end
end
@obj
end

I suppose that has the same problem, depending on implementation, in
that the method definition could be reordered before the assignment…

[1] Double-checked locking - Wikipedia

On Tue, 12 Jun 2007 05:42:02 +0900, “Nasir K.” [email protected]
wrote:

While the comparison is against Java synchronization, does the
Mutex#synchronize has the same behavior as synchronized in Java?

Roughly speaking, yes.

end

The keyword “synchronized” in Java implies not just locking but perhaps
more importantly “synchronization” of the current thread’s working memory with
heap. [JLS 17.4]

i.e. memory barriers.

It’s probably better to compare Mutex#lock and Mutex#unlock with the JVM
monitorenter and monitorexit instructions, since Java’s synchronized
translates to a single pair of monitorenter and monitorexit instructions
on the JVM.

In JRuby, a call to each of #lock and #unlock involves both monitorenter
and monitorexit, so Mutex#synchronize is actually slightly more
aggressive than Java synchronized in terms of its memory effects.
However, the portable assumption is probably still only that it is no
less aggressive than the JVM.

I can understand the behavior of JRuby but how would the other Ruby
interpreters be implemented for such things?

In practice, implementors defer to whatever the underlying platform is;
under those circumstances, the only option for programmers is to adopt a
very conservative posture towards locking and memory model.

If one were to implement a Ruby interpreter today where should he/she
start in the absence of a language spec? Take MRI as the spec? But MRI may
not be addressing all the issues like the one in question here.

Indeed – it doesn’t. Though I suppose one could reasonably say that
MRI, with its green threads, effectively fences all reads and writes,
but that’s obviously a bit heavy-handed to actually implement.

Yes, but even the volatile keyword doesn’t do what you expect. Despite
widespread folklore, volatile by itself is not sufficient to ensure
thread safety – you need to use operations which include memory barriers
(generally by using the provided synchronization primitives).

BTW the folklore is codified as JLS v3 and is available since Java 5. (JSR
133 fixed in 2004)

That was badly needed (and I believe a similar fix was made in the CLR
2.0 memory model as well), but JRuby still targets Java 4, as does
nearly all of other Java code I’ve been asked to work on professionally
– there’s a bit of a gap between a JSR going in, and that Java version
being widely adopted.

(Admittedly, I may be behind the curve Java 5 adoption, and I’m less
sure about CLR 2.0 adoption as a whole.)

Anyway, for right now, this is a moot point in Ruby, since
double-checked locking isn’t possible to implement safely there; we have
neither volatile (broken or otherwise), nor anything like .NET’s
System.Threading.Thread.MemoryBarrier(). The only way to get the
required memory barriers in Ruby is to trust the library locking
primitives to provide them when they are needed. The safe thing is to
simply never to perform accesses to shared variables outside the
protection of a lock, since that being safe is the minimum guarantee any
locking implementation must provide.

-mental

On Tue, 12 Jun 2007 07:32:11 +0900, Joel VanderWerf
[email protected] wrote:

So you’re saying that, as the line

     @obj = Whatever.new

is executed, it is possible (in some ruby implementations, but not MRI
1.8) that @obj will be assigned the Whatever instance before the #new
call completes? (That does seem to be what the wikipedia entry is
warning about.[1])

More or less. Some of this is a result of instruction reordering
performed by both the compiler and the CPU, and some of this is the
result of stuff not getting written out to shared memory right away.
Either way, it’s possible for one thread’s operations to appear in a
different order to other threads.

The effect of a memory barrier is to bring all threads involved into
sync, so to speak, so that whatever is going on behind the scenes, they
all see things happening in the same order. Memory barriers are the
blue pill – you really don’t want to see how deep the concurrency
rabbit hole goes.

 @obj

end

I suppose that has the same problem, depending on implementation, in
that the method definition could be reordered before the assignment…

There’s also a race condition. Consider:

thread #1: enters #obj
thread #2: enters #obj
thread #2: acquires @lock
thread #1: waits for @lock
thread #2: @obj = Whatever.new (#Whatever:0x1234abcd)
thread #2: redefines obj
thread #2: releases @lock
thread #2: returns @obj (#Whatever:0x1234abcd)
thread #1: wakes up
thread #1: acquires @lock
thread #1: @obj = Whatever.new (#Whatever:0xcafebeef)
thread #1: redefines obj
thread #1: releases @lock
thread #1: returns @obj (#Whatever:0xcafebeef)

Sometimes when writing multi-threaded code, it helps to pretend that the
thread scheduler hates your guts and has it in for you personally. I
think you’d have to do something like this at least:

def obj
@lock.synchronize do
@obj ||= Whatever.new
def self.obj
@obj
end
@obj
end
end

But there’s still the un-synchronized read of @obj from within the
singleton method, which can even occur before @lock has been released by
the writing thread. It might often work in practice, since the Ruby
implementation is (hopefully) doing some kind of synchronization when
reading/updating its method tables, preventing the CPU from reordering
things across the method definition. But a lot depends on the memory
model, and there’s some pretty crazy stuff out there (cough Alpha
*cough)…

It’s probably best to stick to the simplest correct way:

def obj
@lock.synchronize do
@obj ||= Whatever.new
end
end

If the locking turns out to be a bottleneck, then the method’s callers
can cache its result in variables private to their respective threads,
which will not require any locking at all to read.

-mental