I want to add a Phobos module with template mixins for common idioms.
Idan Arye
GenericNPC at gmail.com
Sun May 19 15:09:11 PDT 2013
On Sunday, 19 May 2013 at 20:35:06 UTC, Diggory wrote:
>>
>> There is no point in saving a thread local reference to the
>> global instance. The `__gshared` instance is never changed
>> once initialized, so if we saved a thread local reference, it
>> would *always* be either null or the same as the `__gshared`
>> one - which means that if the local reference is not null,
>> there is no difference between returning the local and the
>> global references.
>>
>> `hasInstance` does not need no synchronization - it would just
>> slow it down. Synchronization is redundant in readonly and
>> writeonly scenarios - and this is a readonly scenario. A
>> single read is atomic with or without a synchronization.
>>
>> At any rate, using my implementation was broekn - I forgot to
>> set the thread local boolean instantiation indicator to
>> true(which would mean there will always be a lock!). I fixed
>> it. Thanks for pointing that out!
>
> With regard to using a boolean instead of storing the instance
> thread locally - you're still reading from a mutable __gshared
> variable with no synchronisation on the reader's side, and that
> is always a bug. It may work in most cases but due to
> instruction reordering and differences between architectures
> there's no guarantee of that.
>
> It's also less efficient as you have to read both the
> thread-local boolean and the __gshared instance. Since the
> thread-local boolean is likely going to use a word anyway you
> may as well store the instance in there instead.
>
> Single reads are NOT atomic. On x86 word-aligned reads *happen*
> to be atomic, and even that is not guaranteed on other
> architectures. The main advantage of the low-lock singleton
> idea is that it is completely independent of architecture
> (there are more efficient ways if the architecture is known).
>
> With respect to "hasInstance", what is a possible use-case
> where synchronisation is not required?
Reading an aligned word-sized value should be atomic, pointers
are word-sized, and compilers usually align variables - so I do
believe it's safe to treat reads as atomic.
And even if they weren't - there should be no problem regarding
`hasInstance`, because it returns a boolean value - true or
false. That means there are for options:
1) `hasInstance` returns true when the instance was already
initialized.
2) `hasInstance` returns false when the instance was not yet
initialized.
3) `hasInstance` returns false while the instance is being
initialized in another thread.
4) `hasInstance` returns true while the instance is being
initialized in another thread.
Obviously we have no problem with 1 and 2, but 3 and 4 seem
problematic. I claim they are not!
Let's take a look at 3. `hasInstance` returns false even thought
an instance is currently being initialized. But if `hasInstace`
was invoked a nanosecond earlier it would have also returned
false, and this time it would be correct. In both
cases(nanosecond earlier and nanosecond later), by the time
thread that called `hasInstance` has reach the next command the
singleton will be instantiated and the answer of `hasInstance`
will be outdated. So - scenario 3 does not introduce any new bugs.
Scenario 4, however, seems to be a bit more problematic - when
`hasInstance` returns true even though the singleton has not been
fully instantiated. What does that mean? well, the only way a
read or a write can be non-atomic is if the memory is
read/written in parts:
- Maybe the read split - maybe `hasInstance` read half the
variable, then the other thread written, then `hasInstance` read
the other half.
- Maybe the write split - maybe the other thread wrote half the
variable, then `hasInstance` read it, then the other thread wrote
the other half.
- And maybe both of them split.
At any rate, no matter was was split, the result is the same -
`hasInstance` will return true because what it read from the
global reference is not null(even though it's not a full
reference). This seems bad - `hasInstance` tells us that the
singleton is already instantiated even though the other thread
didn't finish writing the reference before it was invoked!
Well, I say it doesn't matter. By the time `hasInstance` will
return it's value and the code can act upon it, the thread that
finished the singleton will surely have finished writing that
single global reference!
And even if it didn't - we can rest assured that we can rely on
`hasInstance`'s answer, because it tell us that the singleton can
no longer be initialized, and that by the time we need to access
it, it will already be initialized. And even if by then the other
thread would still not finished writing the reference - maybe
because the thread that called `hasInstance` is being run on a
3.5GHz core and the thread that initiates the singleton instance
is being run by a monkey with an abacus - once you try to
actually access that instance you will enter that synchronization
block that will hold you until the monkey finish writing the
reference and it is ready to use.
Beside `hasInstance`, there is two other place where I am reading
the `__gshared` reference without synchronization - both at
`instance()`. One is a null check in case singleton does not
support implicit instantiation - and now that I look at it, maybe
I should put a synchronization there(though I still don't think
it's needed). The other one is the return statement - but by the
time we reach it, we know that the instantiation was already
completed, and we know that the the reference will never change
again until the end of the process - in other words, we know
there will not be any more writes to this `__gshared` reference,
and that means we can read it safely without synchronizations
just we can safely read immutable values without synchronization.
More information about the Digitalmars-d
mailing list