On Monday, 15 October 2018 at 21:51:43 UTC, Manu wrote:
If a shared method is incompatible with an unshared method,
your class is broken.
What?!? So... my unshared methods should also perform all that's
necessary for `shared` methods?
Explicit casting doesn't magically implement thread-safety, it
basically just guarantees failure.
It doesn't indeed. It does, however, at least help prevent silent
bugs. Via that same guaranteed failure. That failure is about all
the help we can get from the compiler anyway.
What I suggest are rules that lead to proper behaviour with
respect to writing a thread-safe API.
You can write bad code with any feature in any number of ways.
Yup. For example, passing an int* to a function expecting shared
int*.
I see it this way:
If your object has shared methods, then it is distinctly and
*deliberately* involved in thread-safety. You have deliberately
opted-in to writing a thread-safe object, and you must deliver
on your promise.
The un-shared API of an object that supports `shared` are not
exempt from the thread-safety commitment, they are simply the
subset of the API that may not be called from a shared context.
And therefore they lack any synchronization. So I don't see how
they *can* be "compatible" with `shared` methods.
If your shared method is incompatible with other methods, your
class is broken, and you violate your promise.
Nope.
class BigCounter {
this() { /* don't even need the mutex if I'm not sharing this
*/ }
this(Mutex m = null) shared {
this.m = m ? m : new Mutex;
}
void increment() { value += 1; }
void increment() shared { synchronized(m)
*value.assumeUnshared += 1; }
private:
Mutex m;
BigInt value;
}
They're not "compatible" in any shape or form. Or would you have
the unshared ctor also create the mutex and unshared increment
also take the lock? What's the point of having them then? Better
disallow mixed implementations altogether (which is actually not
that bad of an idea).
Nobody writes methods of an object such that they don't work
with each other... methods are part of a deliberately crafted
and packaged
entity. If you write a shared object, you do so deliberately,
and you buy responsibility of making sure your objects API is
thread-safe.
If your object is not thread-safe, don't write shared methods.
Ahem... Okay...
import std.concurrency;
import core.atomic;
void thread(shared int* x) {
(*x).atomicOp!"+="(1);
}
shared int c;
void main() {
int x;
auto tid = spawn(&thread, &x); // "just" a typo
}
You're saying that's ok, it should "just" compile. It shouldn't.
It should produce an error and a mild electric discharge into the
developer's chair.