On Wed, Oct 17, 2018 at 10:30 AM Steven Schveighoffer via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > > On 10/17/18 12:27 PM, Nicholas Wilson wrote: > > On Wednesday, 17 October 2018 at 15:51:04 UTC, Steven Schveighoffer wrote: > >> On 10/17/18 9:58 AM, Nicholas Wilson wrote: > >>> On Wednesday, 17 October 2018 at 13:25:28 UTC, Steven Schveighoffer > >>> wrote: > >>>> It's identical to the top one. You now have a new unshared reference > >>>> to shared data. This is done WITHOUT any agreed-upon synchronization. > >>> > >>> It isn't, you typo'd it (I originally missed it too). > >>>> int *p3 = cast(int*)p2; > >>> > >>> vs > >>> > >>>> int *p3 = p; > >> > >> It wasn't a typo. > > > > The first example assigns p2, the second assigns p (which is thread > > local) _not_ p2 (which is shared), I'm confused. > > > > Here they are again: > > int *p; > shared int *p2 = p; > int *p3 = cast(int*)p2; > > int *p; > shared int *p2 = p; > int *p3 = p; > > > I'll put some asserts in that show they accomplish the same thing: > > assert(p3 is p2); > assert(p3 is p); > assert(p2 is p); > > 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. There's only one owning thread, and you can't violate that without unsafe casts. > 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. > That's why shared/unshared is more akin to mutable/immutable than > mutable/const. Only if you misrepresent my suggestion. > 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. 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); } } 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. 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.