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