`shared`...

Steven Schveighoffer schveiguy at gmail.com
Mon Oct 1 23:56:22 UTC 2018


On 10/1/18 7:09 PM, Manu wrote:
> On Mon, Oct 1, 2018 at 8:55 AM Timon Gehr via Digitalmars-d
> <digitalmars-d at puremagic.com> wrote:
>>
>> On 01.10.2018 04:29, Manu wrote:
>>> struct Bob
>>> {
>>>     void setThing() shared;
>>> }
>>>
>>> As I understand, `shared` attribution intends to guarantee that I dun
>>> synchronisation internally.
>>> This method is declared shared, so if I have shared instances, I can
>>> call it... because it must handle thread-safety internally.
>>>
>>> void f(ref shared Bob a, ref Bob b)
>>> {
>>>     a.setThing(); // I have a shared object, can call shared method
>>>
>>>     b.setThing(); // ERROR
>>> }
>>>
>>> This is the bit of the design that doesn't make sense to me...
>>> The method is shared, which suggests that it must handle
>>> thread-safety. My instance `b` is NOT shared, that is, it is
>>> thread-local.
>>> So, I know that there's not a bunch of threads banging on this
>>> object... but the shared method should still work! A method that
>>> handles thread-safety doesn't suddenly not work when it's only
>>> accessed from a single thread.
>>> ...
>>
>> shared on a method does not mean "this function handles thread-safety".
>> It means "the `this` pointer of this function is not guaranteed to be
>> thread-local". You can't implicitly create an alias of a reference that
>> is supposed to be thread-local such that the resulting reference can be
>> freely shared among threads.
> 
> I don't understand. That's the point of `scope`... is that it won't
> escape the reference. 'freely shared' is the antithesis of `scope`.
> 
>>> I feel like I don't understand the design...
>>> mutable -> shared should work the same as mutable -> const... because
>>> surely that's safe?
>>
>> No. The main point of shared (and the main thing you need to understand)
>> is that it guarantees that if something is _not_ `shared` is is not
>> shared among threads. Your analogy is not correct, going from
>> thread-local to shared is like going from mutable to immutable.
> 
> We're talking about `mutable` -> `shared scope`. That's like going
> from mutable to const.
> `shared scope` doesn't say "I can share this", what it says is "this
> may be shared, but *I won't share it*", and that's the key.
> By passing a thread-local as `shared scope`, the receiver accepts that
> the argument _may_ be shared (it's not in this case), but it will not
> become shared in the call. That's the point of scope, no?
> 
>> If the suggested typing rule was implemented, we would have the
>> following way to break the type system, allowing arbitrary aliasing
>> between mutable and shared references, completely defeating `shared`:
>>
>> class C{ /*...*/ }
>>
>> shared(C) sharedGlobal;
>> struct Bob{
>>       C unshared;
>>       void setThing() shared{
>>           sharedGlobal=unshared;
>>       }
>> }
>>
>> void main(){
>>       C c = new C(); // unshared!
>>       Bob(c).setThing();
>>       shared(D) d = sharedGlobal; // shared!
>>       assert(c !is d); // would fail (currently does not even compile)
>>       // sendToOtherThread(d);
>>       // c.someMethod(); // (potential) race condition on unshared data
>> }
> 
> Your entire example depends on escaping references. I think you missed
> the point?
> 

The problem with mutable wildcards is that you can assign them.

This exposes the problem in your design. The reason const works is 
because you can't mutate it. Shared is not the same.

simple example:

void foo(scope shared int *a, scope shared int *b)
{
    a = b;
}

If I can bind a to a local mutable int pointer, and b as a pointer to 
global shared int, the assignment is now considered OK (types and scopes 
are the same), but now my local points at a shared int without the 
shared adornments.

The common wildcard you need between shared and mutable is *unique*. 
That is, even though it's typed as shared or unshared, the compiler has 
guaranteed there is no other reference to that data. In that case, you 
can move data from one place to another without compromising the system 
(as you assign from one unique pointer to another, the original must 
have to be nullified, otherwise the wildcard still would not work, and 
the unique property would cease to be accurate).

IMO, the correct way to deal with shared would be to make it 100% 
unusable. Not readable, or writable. And then you have to cast away 
shared to make it work (and hopefully performing the correct locking to 
make sure your changes are defined). I don't think there's a magic 
bullet that can fix this.

-Steve


More information about the Digitalmars-d mailing list