Very limited shared promotion

Jonathan M Davis newsgroup.d at jmdavisprog.com
Mon Jun 24 13:20:23 UTC 2019


On Monday, June 24, 2019 5:38:29 AM MDT aliak via Digitalmars-d wrote:
> On Monday, 24 June 2019 at 02:55:39 UTC, Walter Bright wrote:
> > On 6/23/2019 7:42 PM, Timon Gehr wrote:
> >> I really don't understand this "before" without memory
> >> barriers. Without memory barriers, there is no "before". How
> >> to formalize this? What is it exactly that "scope" guarantees?
> >
> > The reference to shared i does not extend past the end of the
> > function call. That doesn't mean the cached value of i is
> > propagated to all other threads.
> >
> >     int x;
> >     void fun(scope ref shared(int) x) {
> >
> >         x = 3; // (*)
> >
> >     } // end of reference to x is guaranteed
> >     assert(x == 3); // all caches not updated, assert is not
> >
> > guaranteed
> >
> > (*) now replace this with a call to another thread to set x to
> > 3.
>
> You can assign to a shared primitive without a compilation error?
>
> Does the compiler do the necessary synchronization under the hood?
>
> I would've assumed you'd have to use atomicStore or the like?

A number of us think that simply reading or assigning shared variables
should be illegal, because the compiler doesn't do the necessary
synchronization stuff for you. Currently, the compiler prevents it for ++
and -- on than basis, but that's it.

However, one issue with that from a usability perspective is that even if
you're doing all of the right stuff like protecting access to the shared
variable via a mutex and then temporarily casting it to thread-local to
operate on it while the mutex is locked, if you want to actually assign
anything to the shared variable before releasing the mutex, you'd have to
use a pointer to it that had been cast to thread-local, whereas right now,
you're actually allowed to just assign to it. e.g.

shared MyClass obj;

synchronized(mutex)
{
    obj = cast(shared)someObj;
}

works right now, but if writing to shared variables were illegal like it
arguably should be, then you'd have to do something ugly like

shared MyClass obj;

synchronized(mutex)
{
    auto ptr = cast(MyClass*)&obj;
    *ptr = someObj;
}

Either way, it's quite clear that as things stand, reading or writing a
shared variable without doing something with threading primitives is
non-atomic and won't be synchronized properly. So, arguably, the type system
shouldn't allow it (and that was started by disalowing ++ and --, but it
wasn't ever finished). However, when this was discussed at this last dconf,
Walter and Andrei didn't want to make any changes along those lines until
we'd nailed down the exact semantics of how shared is supposed to work.
Right now, shared is pretty much just defined as not being thread-local
without the finer details really being ironed out.

So, for now, shared does prevent you from accidentally converting between
thread-local and shared, but on the whole, it doesn't actually do anything
to prevent you from doing stuff to a shared variable that isn't going to be
atomic or thread-safe, and it doesn't introduce stuff like fences to
synchronize anything across threads. It's entirely up to the programmer to
use it correctly with almost no help from the compiler. All it's really
doing is segregating the shared data from the thread-local data and
preventing you from crossing that barrier without casting, making such code
@system. And that's definitely something useful, but the specification needs
to be fully nailed down, and there are clearly some improvements that need
to be made - though of course, a lot of the arguing is over the changes that
should be made.

 Jonathan M Davis





More information about the Digitalmars-d mailing list