On 8/7/14, 11:22 AM, Peter Alexander wrote:
On Thursday, 7 August 2014 at 16:44:37 UTC, Andrei Alexandrescu wrote:
Yah. The basic intent behind "shared" is to disallow unwitting racy
code, and allow users to define robust shared abstractions.
Consider this code at top level:
struct S {
private long a, b, c;
...
}
shared S g_theS;
All threads will "see" the same g_theS. The intent of "shared" is to
disallow racy/wrong use of g_theS across threads. However, assigning
g_theS is by default allowed and does something definitely racy.
So I think we should do the following:
* if S.sizeof <= size_t.sizeof, generate atomic assignment
automatically for shared values.
* if S.sizeof == 2 * size_t.sizeof, generate special 128-bit atomic
assignment for shared values on 64-bit systems, and 64-bit atomic
assignment on 32-bit systems.
* In all other cases, reject code during compilation and require an
opAssign for shared values.
This will break code. It may even break correct code (benign races or
code that uses external locking). On the other hand, it seems like the
right thing to do by the intended semantics of "shared".
Even with that change, you'll still be able to do:
S otherS = ...;
g_theS.a = otherS.a;
g_theS.b = otherS.b;
g_theS.c = otherS.c;
Right? This violates the same invariants as:
Not if they're private. Access to non-encapsulated data is not supposed
to be regulated by the language.
g_theS = otherS;
Can you explain the practical benefits of enforcing atomicity of shared
objects only at the individual statement level? I must be missing
something.
The whole idea behind shared is disallowing implicit races, a common
source of bugs. All shared data is assumed to be visible to several
threads at once, and as such is subject to additional restrictions
compared to non-shared data, all of which is considered thread-local.
That's why e.g. recently RMW operations on shared integrals were disallowed.
Andrei