On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
It seems that methods qualified with 'shared' may be what
you're suggesting matches up with the 'bridge' I'm trying to
describe, but again, using the word 'shared' to mean both
'thread safe' and 'not thread safe' doesn't make sense. [...]
For essentially all that follows, refer to [1][2]
`shared` (as well as `__gshared`) on a variable has the semantics
of multiple threads sharing one single memory location for that
variable (i.e. it will not be put into thread local storage).
Accessing such data directly is inherently not thread safe.
Period. You will need some form of synchronization (see [3]) to
access such data in a thread safe manner.
Now, `shared` is supposed to additionally provide memory
barriers, so that reads/writes on such variables are guaranteed
not to be reordered in a way that breaks your algorithm;
remember, the compiler (and also later the cpu when it reorders
the opcode) is allowed to reorder reads/writes to a memory
location to be more efficient, as long as doing so won't change
the logic as the compiler/or cpu sees it. Example:
__gshared int a = 0;
// thread 1:
a = 1;
int b = a;
writeln(b);
// thread 2:
a += 1;
In the above, you may expect `b` to be either 1, or 2, depending
on how the cpu interleaves the memory access, but it can, in
fact, also be 0, since neither the compiler, nor the cpu can
detect any reason as to why `a = 1` should need to come before
`int b = a` and may thus reorder the write and the read. Memory
barriers prevent such reordering in the cpu and if we had made
`a` `shared` those barriers would've been supposed to be emitted
by the compiler (in addition to not reordering them itself).
Unfortunately, that emission is not implemented.
From [4]:
Non-static member functions can have, in addition to the usual
FunctionAttributes, the attributes const, immutable, shared, or
inout. These attributes apply to the hidden this parameter.
Thus a member function being `shared` means nothing more than
that the instance it is called on must also be `shared`, i.e.
class Foo
{
shared void bar();
}
Foo foo;
foo.bar(); // this is illegal, `foo` (the hidden `this` of
`bar`) is not shared
shared Foo foobar;
foobar.bar(); // this is legal, since `foobar` is shared
That's it, there are no two meanings of `shared` depending on
some context, there is only one: The data in question, which is
either the attributed variable, or the object/instance of the
member function being attributed, is shared between threads and
accessing it directly is not thread safe.
On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
I thought 'shared' was a finished feature, but it's starting to
seem like it's a WIP.
I prefer the term "unfinished" since "WIP" implies that it's
being worked on. AFAIK there's no one currently working on
implementing what's missing in the compiler frontend with regards
to the spec.
On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
This kind of feature seems like it has great potential, but is
mostly useless in it's current state.
I share that opinion and generally either use `__gshared` if I
absolutely have to share data via shared memory and design
carefully to avoid all the potential issues, or - which I much
prefer - use message passing: `std.concurrency` is your friend.
On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
After more testing with shared, it seems that 'shared' data is
mutable from many contexts, from which it would be unsafe to
mutate it without locking first, which basically removes any
gauruntee that would make 'shared' useful.
As pointed out above that's to be expected, since that's its job.
Regarding guarantees: Since D treats data as thread local by
default, you need either `shared` or `__gshared` to have mutable
shared (intra-process) memory (ignoring OS facilities for
inter-process shared memory). The main advantage is not in data
being `shared`/`__gshared`, but in the guarantees that all the
other (unattributed, thread local) data gets: Each thread has its
own copies and any optimisations applied to code that accesses
them need not consider multiple threads (I'd wager this is a
significant boon towards D's fast compile times).
If you only talk about useful benefits of `shared` over
`__gshared`, if the spec were properly implemented, the useful
properties would include you not needing to worry about memory
barriers. Other useful guaranties are the more rigorous type
checks, when compared to `__gshared`, which are supposed to
prevent you from committing some of the common mistakes occurring
in non-sequential programming (see, e.g. the code example with
`class Foo` above).
On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
Again, tell me if I'm wrong here, but there seems to be a lot
of holes in 'shared'.
There are holes in the implementation of `shared`; it's spec,
however, is complete and coherent.
[1] https://dlang.org/faq.html#shared_guarantees
[2] https://dlang.org/spec/attribute.html#shared
[3]
https://tour.dlang.org/tour/en/multithreading/synchronization-sharing
[4] https://dlang.org/spec/class.html