Very limited shared promotion
Walter Bright
newshound2 at digitalmars.com
Wed Jun 19 21:03:44 UTC 2019
On 6/19/2019 6:45 AM, Timon Gehr wrote:
> On 19.06.19 11:13, Walter Bright wrote:
>>
>> Using the resulting value of x:
>>
>> int x;
>> void fun(scope ref shared(int) x) { ... }
>> fun(x); // implicit promotion to shared in this case
>> int y = x; // add this line
>>
>> And now there's a problem. The compiler thinks x is thread local, so it
>> doesn't add synchronization to the read of x.
>
> Probably `fun` should be `immutable` or `shared` as well. Otherwise there is no
> way to tell at the call site that it does not also capture `x` mutably, in which
> case the implicit promotion couldn't be allowed.
`scope` prevents capture of a reference to `x`, mutable or not, that persists
after the return from `fun`.
>> Not adding synchronization means that although fun() has terminated all the
>> threads it spawned accessing x, it does not mean the memory caches of x are
>> updated.
>> ...
>
> The @trusted code that decided to send the `scope` reference to other threads
> has to make sure that all reads/writes to the `scope`d reference context are
> visible on the current thread before it returns. (Otherwise, from the
> perspective of the current thread, there are still copies of the `scope`
> reference around, even though the function call has terminated, which goes
> against `scope` guarantees.)
The way atomic synchronization of variables works is when a write is performed,
a write access fence happens after. When a read access is performed, a read
access fence is performed.
The last write to shared x in fun will do a write access fence, but the read
access fence won't happen until there's the read of shared x, the read access
fence won't happen. But the x after the return of fun is not shared, no read
access fence will happen, and the read may not see the results of the last write
to shared x.
>> While you may have coded fun() to update the caches of x before returning, the
>> compiler can't know that, and so the compiler cannot allow the implicit
>> conversion.
>> ...
>
> The compiler can allow the implicit conversion because in @safe code there is no
> way to send `scope` data to other threads and @trusted code has to respect
> `scope` guarantees.
The scope guarantees are NOT sequential consistency data-race-free guarantees
for multithreaded access.
>> In other words, adding `scope` does not guarantee SC-DRF (Sequential
>> Consistency - Data Race Free).
> I think it does. The challenge is to ensure that the implicit promotion to
> `shared` doesn't result in `shared`/unshared aliasing. I don't think there is a
> problem with implicitly casting back to unshared, as the called function
> promises not to leak the references.
It does not. A write to an atomic is:
write variable
read fence
A read from an atomic is:
read fence
read variable
SC-DRF of atomics absolutely depends on this. The example code does:
thread 1:
write x
read fence
thread 2:
read x
This is wrong wrong wrong from what I know about atomics.
The only way a local reference can be implicitly converted to a shared reference
is if the compiler can prove there are no other local references to that memory
location, which is essentially what Rust does. The read of `x` after `fun`
returns violates that principle, and the result is a data race.
---
There's another way to look at this:
1. When a politician promises a Free Lunch, you're gonna pay for it.
2. When a scientist discovers perpetual motion, he's made a mistake.
3. When a salesman sells you an effortless exercise machine, you won't get stronger.
4. When you respond to an ad for a "Get Rich Quick Through Real Estate" seminar,
you're a sucker.
-- and --
5. When a data-race-free solution is found that doesn't follow fencing
protocols, it doesn't work.
More information about the Digitalmars-d
mailing list