On Monday, 13 February 2017 at 14:20:05 UTC, Kagamin wrote:
Thread unsafe methods shouldn't be marked shared, it doesn't
make sense. If you don't want to provide thread-safe interface,
don't mark methods as shared, so they will not be callable on a
shared instance and thus the user will be unable to use the
shared object instance and hence will know the object is thread
unsafe and needs manual synchronization.
To be clear: While I might, in general, agree that using shared
methods only for thread safe methods seems to be a sensible
restriction, neither language nor compiler require it to be so;
and absence of evidence of a useful application is not evidence
of absence.
On Monday, 13 February 2017 at 14:20:05 UTC, Kagamin wrote:
ps Memory barriers are a bad idea because they don't defend
from a race condition, but they look like they do :)
There are two very common pitfalls in non-sequential programming
with regards to reads/writes to memory shared between threads:
Issue 1: Sequencing/Interleaving of several threads into the
logical memory access order
Issue 2: Reordering of code within one thread
Code that changes semantics because of issue 1 has race
conditions; fixing it requires synchronization primitives, such
as locking opcode, transactional memory, etc.
Code that changes semantics because of issue 2 may or may not
have race conditions, but it definitely requires memory barriers.
Claiming that memory barriers are a bad idea because they don't
defend against race conditions, but look like they do (when
that's what synchronization is for) is similar enough to saying
airbags in cars are a bad idea because they don't keep your body
in place, but look like they do (when that's what seat belts are
for).
My point here being that I don't understand what made you state
that memory barriers look like they deal with race conditions, as
they have nothing to do with that.
To be clear: Synchronization (the fix for race conditions) does
not help you to deal with issue 2. If my last example had instead
been
---
__gshared int f = 0, x = 0;
Object monitor;
// thread 1
synchronized (monitor) while (f == 0);
// Memory barrier required here
synchronized (monitor) writeln(x)
// thread 2
synchronized (monitor) x = 42;
// Memory barrier required here
synchronized (monitor) f = 1;
---
you'd still need those memory barriers. Also note that the
synchronization in the above is not needed in terms of semantics.
The code has no race conditions, all permutations of the
(interleaved) memory access order yield the same output from
thread 1. Also, since synchronization primitives and memory
barriers have different runtime costs, depending on your hardware
support and how they are translated to that support from D,
there's no "one size fits all" solution on the low level we're on
here.
My opinion on the matter of `shared` emitting memory barriers is
that either the spec and documentation[1] should be updated to
reflect that sequential consistency is a non-goal of `shared`
(and if that is decided this should be accompanied by an example
of how to add memory barriers yourself), or it should be
implemented. Though leaving it in the current "not implemented,
no comment / plan on whether/when it will be implemented" state
seems to have little practical consequence - since no one seems
to actually work on this level in D - and I can thus understand
why dealing with that is just not a priority.
On Monday, 13 February 2017 at 14:20:05 UTC, Kagamin wrote:
use std.concurrency for a simple and safe concurrency, that's
what it's made for.
I agree, message passing is considerably less tricky and you're
unlikely to shoot yourself in the foot. Nonetheless, there are
valid use cases where the overhead of MP may not be acceptable.
[1] https://dlang.org/faq.html#shared_guarantees