On 10/17/18 2:46 PM, Manu wrote:
On Wed, Oct 17, 2018 at 10:30 AM Steven Schveighoffer via

What the example demonstrates is that while you are trying to disallow
implicit casting of a shared pointer to an unshared pointer, you have
inadvertently allowed it by leaving behind an unshared pointer that is
the same thing.

This doesn't make sense... you're showing a thread-local program.
The thread owning the unshared pointer is entitled to the unshared
pointer. It can make as many copies at it likes. They are all
thread-local.

It's assumed that shared int pointer can be passed to another thread, right? Do I have to write a full program to demonstrate?

There's only one owning thread, and you can't violate that without unsafe casts.

The what is the point of shared? Like why would you share data that NOBODY CAN USE?

At SOME POINT, shared data needs to be readable and writable. Any correct system is going to dictate how that works. It's a good start to make shared data unusable unless you cast. But then to make it implicitly castable from unshared defeats the whole purpose.

In order for a datum to be
safely shared, it must be accessed with synchronization or atomics by
ALL parties.

** Absolutely **

If you have one party that can simply change it without
those, you will get races.

*** THIS IS NOT WHAT I'M PROPOSING ***

I've explained it a few times now, but people aren't reading what I
actually write, and just assume based on what shared already does that
they know what I'm suggesting.
You need to eject all presumptions from your mind, take the rules I
offer as verbatim, and do thought experiments from there.

What seems to be a mystery here is how one is to actually manipulate shared data. If it's not usable as shared data, how does one use it?


That's why shared/unshared is more akin to mutable/immutable than
mutable/const.

Only if you misrepresent my suggestion.

It's not misrepresentation, I'm trying to fill in the holes with the only logical possibilities I can think of.


It's true that only one thread will have thread-local access. It's not
valid any more than having one mutable alias to immutable data.

And this is why the immutable analogy is invalid. It's like const.
shared offers restricted access (like const), not a different class of
thing.

No, not at all. Somehow one must manipulate shared data. If shared data cannot be read or written, there is no reason to share it.

So LOGICALLY, we have to assume, yes there actually IS a way to manipulate shared data through these very carefully constructed and guarded things.

There is one thread with thread-local access, and many threads with
shared access.

If a shared (threadsafe) method can be defeated by threadlocal access,
then it's **not threadsafe**, and the program is invalid.

struct NotThreadsafe
{
   int x;
   void local()
   {
     ++x; // <- invalidates the method below, you violate the other
function's `shared` promise
   }
   void notThreadsafe() shared
   {
     atomicIncrement(&x);
   }
}

So the above program is invalid. Is it compilable with your added allowance of implicit casting to shared? If it's not compilable, why not? If it is compilable, how in the hell does your proposal help anything? I get the exact behavior today without any changes (except today, I need to explicitly cast, which puts the onus on me).


struct Atomic(T)
{
   void opUnary(string op : "++")() shared { atomicIncrement(&val); }
   private T val;
}
struct Threadsafe
{
   Atomic!int x;
   void local()
   {
     ++x;
   }
   void threadsafe() shared
   {
     ++x;
   }
}

Naturally, local() is redundant, and it's perfectly fine for a
thread-local to call threadsafe() via implicit conversion.

In this case, yes. But that's not because of anything the compiler can prove.

How does Atomic work? I thought shared data was not usable? I'm being pedantic because every time I say "well at some point you must be able to modify things", you explode.

Complete the sentence: "In order to read or write shared data, you have to ..."


Here's another one, where only a subset of the object is modeled to be
threadsafe (this is particularly interesting to me):

struct Threadsafe
{
   int x;
   Atomic!int y;

   void notThreadsafe()
   {
     ++x;
     ++y;
   }
   void threadsafe() shared
   {
     ++y;
   }
}

In these examples, the thread-local function *does not* undermine the
threadsafety of threadsafe(), it MUST NOT undermine the threadsafety
of threadsafe(), or else threadsafe() **IS NOT THREADSAFE**.
In the second example, you can see how it's possible and useful to do
thread-local work without invalidating the objects threadsafety
commitments.


I've said this a bunch of times, there are 2 rules:
1. shared inhibits read and write access to members
2. `shared` methods must be threadsafe

From there, shared becomes interesting and useful.


Given rule 1, how does Atomic!int actually work, if it can't read or write shared members?
For rule 2, how does the compiler actually prove this?

Any programming by convention, we can do today. We can implement Atomic!int with the current compiler, using unsafe casts inside @trusted blocks.

-Steve

Reply via email to