www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - shared vs __gshared

reply "Sergei Nosov" <sergei.nosov gmail.com> writes:
Hi!

I'm puzzled with what's the difference between shared and 
__gshared. Until now I've (mistakenly) considered that shared 
variables are free from low-level data races. I.e. the operations 
with shared data are atomic. And that __gshared is the "usual" 
(in C++ sense) shared data.

So, in my understanding, it was that if I do

shared int s = 0;
void inc()
{
     s++;
}
void main()
{
     foreach (i; iota(100_000).parallel)
         s++;
}

I will always get s == 100_000 by the end of foreach loop. And if 
I use __gshared specifier, I will get the undefined value less or 
equal 100_000.

But now I've run the test and I get the "undefined value" result 
for both shared and __gshared.

I've looked this up in TDPL, and careful reading got me that 
shared guarantees only that the result of a write is instantly 
visible to all threads. So according to this, I can load the 
value in register, change it there, wait until someone else 
rewrites the value, and put my version on top, discarding someone 
else's result.

Is this how shared is supposed to work? If so, how is the 
__gshared different from it? Does it not guarantee that the 
result of a write is instantly visible to all threads? If so, 
does this difference really matter?
Jun 10 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Sergei Nosov:

 Is this how shared is supposed to work? If so, how is the 
 __gshared different from it? Does it not guarantee that the 
 result of a write is instantly visible to all threads? If so, 
 does this difference really matter?
shared and __gshared are very different things. __gshared guarantees nothing, it's equivalent to a raw C global (module-level) variables. Bye, bearophile
Jun 10 2013
prev sibling next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
10-Jun-2013 17:38, Sergei Nosov пишет:
 Hi!

 I'm puzzled with what's the difference between shared and __gshared.
 Until now I've (mistakenly) considered that shared variables are free
 from low-level data races. I.e. the operations with shared data are
 atomic. And that __gshared is the "usual" (in C++ sense) shared data.
 Is this how shared is supposed to work? If so, how is the __gshared
 different from it? Does it not guarantee that the result of a write is
 instantly visible to all threads? If so, does this difference really
 matter?
Simply put __gshared is a hack to get exactly the same as plain global in C. Nothing about memory visibility is guaranteed unless explicitly enforced with some core.sync/core.atomic primitives. Worse yet it pretends to be the usual thread-local variable in a type system (hence a hack). Shared is above all a transitive qualifier so if you have say struct S{ char* ptr; int len; } shared S s; s.ptr is also typed as shared(char*) Secondly shared type should strongly limit the scope of operations you can do with it - for instance writing normal (TLS) pointer to S.ptr field. This makes it easy to separate what's shared between threads and what is not. In a certain sense it actively prevents inadvertent mixing of thread-local and global state. Continuing with limitations: for shared classes and structs you can only call shared methods on a shared instance. In this case think of shared as "thread-safe" because currently you most likely will have to cast you way though and maintain it by hand. Third is as you notated it's backed by the same simple global data visible by all threads. This is the same for both. Now there was a discussion on it recently which indicates that shared data might lose it's built-in ops to prevent confusion and require folks to just use core.atomic directly for lock-free or alternatively cast+mutex for lock-based. -- Dmitry Olshansky
Jun 10 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Dmitry Olshansky:

 Now there was a discussion on it recently which indicates that 
 shared data might lose it's built-in ops to prevent confusion 
 and require folks to just use core.atomic directly for 
 lock-free or alternatively cast+mutex for lock-based.
Looks like a good idea. Bye, bearophjile
Jun 10 2013
prev sibling parent reply "Sergei Nosov" <sergei.nosov gmail.com> writes:
Thank you for answers. Let me check if I got this right.

On Monday, June 10, 2013 13:23:26 Steven Schveighoffer wrote:
 shared was supposed to infer memory barriers, but AFAIK, it 
 doesn't do
 that. Not sure it ever will.
So, my first impression about what shared should do (no low-level races at all) was correct, but the things didn't work out that way. So that kind of doesn't solve the issue with low-level races, which IIRC Andrei considers the biggest crime a language type system can commit. And the likely (brand-new) solution to that is On Monday, 10 June 2013 at 14:49:27 UTC, Dmitry Olshansky wrote:
 Now there was a discussion on it recently which indicates that 
 shared data might lose it's built-in ops to prevent confusion 
 and require folks to just use core.atomic directly for 
 lock-free or alternatively cast+mutex for lock-based.
which seems reasonable too. So, is my understanding correct? If yes, why the path with memory barriers was "announced", but not taken?
Jun 10 2013
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 10 Jun 2013 22:33:03 -0400, Sergei Nosov <sergei.nosov gmail.com>  
wrote:

 So, is my understanding correct? If yes, why the path with memory  
 barriers was "announced", but not taken?
Having recently acquired (no pun intended) new knowledge on how hard memory races are to solve with language syntax and primitives, I can appreciate that there isn't a silver bullet way to do this. BUT, shared should be something more than just a way to modify a type to say "DON'T USE ME, I SUCK!" I think we need one more mechanism to fix this. Something that allows one to move data from shared to unshared in a reliable and provable way. Because really, you never want to directly work with shared data without claiming and proving ownership of the data, even if temporarily. -Steve
Jun 10 2013
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 10 Jun 2013 09:38:43 -0400, Sergei Nosov <sergei.nosov gmail.com>  
wrote:

 Hi!

 I'm puzzled with what's the difference between shared and __gshared.
shared is part of the type, __gshared is not. The danger of __gshared is that the compiler treats it as if it were not shared. You better know what you are doing, lots of code expects that anything not marked as shared is thread-local. shared was supposed to infer memory barriers, but AFAIK, it doesn't do that. Not sure it ever will. -Steve
Jun 10 2013
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, June 10, 2013 13:23:26 Steven Schveighoffer wrote:
 shared was supposed to infer memory barriers, but AFAIK, it doesn't do
 that. Not sure it ever will.
I believe that things are definitely leaning towards shared not adding memory barriers. There _might_ be something that happens with shared that relates to the GC in order to be able to facilitate per-thread collection, but at this point, I think that it's looking like shared will be almost like const in that it's a compile-time only thing enforced by the type system but has no effect at runtime (the main difference being that objects created as shared will be in shared memory rather than being thread-local - which _would_ be a difference at runtime - but aside from that, there may be no difference between shared and non-shared at runtime). But I believe that ironing out shared is one of the things that Andrei wants to discuss and sort out soon based on his comments in recent discussions on whether we're going to make member functions non-virtual by default. - Jonathan M Davis
Jun 10 2013
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 10 Jun 2013 15:46:27 -0400, Jonathan M Davis <jmdavisProg gmx.com>  
wrote:

 On Monday, June 10, 2013 13:23:26 Steven Schveighoffer wrote:
 shared was supposed to infer memory barriers, but AFAIK, it doesn't do
 that. Not sure it ever will.
I believe that things are definitely leaning towards shared not adding memory barriers. There _might_ be something that happens with shared that relates to the GC in order to be able to facilitate per-thread collection, but at this point, I think that it's looking like shared will be almost like const in that it's a compile-time only thing enforced by the type system but has no effect at runtime (the main difference being that objects created as shared will be in shared memory rather than being thread-local - which _would_ be a difference at runtime - but aside from that, there may be no difference between shared and non-shared at runtime).
Well, I think this should be clarified as "the compiler won't generate special runtime code." Given that shared is a type modifier, run time can already take advantage of knowing whether something is shared or not. For example, appending to unshared arrays avoids taking the GC lock if it can append in place. -Steve
Jun 10 2013