shared - i need it to be useful

Timon Gehr timon.gehr at gmx.ch
Wed Oct 17 12:02:52 UTC 2018


On 16.10.2018 20:07, Manu wrote:
> On Tue, Oct 16, 2018 at 6:25 AM Timon Gehr via Digitalmars-d
> <digitalmars-d at puremagic.com> wrote:
>>
>> On 16.10.2018 13:04, Dominikus Dittes Scherkl wrote:
>>> On Tuesday, 16 October 2018 at 10:15:51 UTC, Timon Gehr wrote:
>>>> On 15.10.2018 20:46, Manu wrote:
>>>>>
>>>>> Assuming the rules above: "can't read or write to members", and the
>>>>> understanding that `shared` methods are expected to have threadsafe
>>>>> implementations (because that's the whole point), what are the risks
>>>>> from allowing T* -> shared(T)* conversion?
>>>>>
>>>>
>>>> Unshared becomes useless, and in turn, shared becomes useless.
>>> why is unshared useless?
>>> Unshared means you can read an write to it.
>>> If you give it to a function that expect something shared,
>>> the function you had given it to can't read or write it, so it
>>> can't do any harm.
>>
>> It can do harm to others who hold an unshared alias to the same data and
>> are operating on it concurrently.
> 
> Nobody else holds an unshared alias.

How so? If you allow implicit conversions from unshared to shared, then 
you immediately get this situation.

> If you pass a value as const, you don't fear that it will become mutable.
> ...

No, but as I already explained last time, mutable -> const is not at all 
like unshared -> shared.

const only takes away capabilities, shared adds new capabilities, such 
as sending a reference to another thread. If you have two threads that 
share data, you need cooperation from both to properly synchronize accesses.

>>> Of course it can handle it threadsave, but as it is local,
>>> that is only overhead - reading or changing the value can't do
>>> any harm either. I like the idea.
>>>
>>>> But useless, because there is no way to ensure thread safety of reads
>>>> and writes if only one party to the shared state knows about the sharing.
>>> Of course there is.
>>
>> Please do enlighten me. You have two processors operating
>> (reading/writing) on the same address space on a modern computer
>> architecture with a weak memory model, and you are using an optimizing
>> compiler. How do you ensure sensible results without cooperation from
>> both of them? (Hint: you don't.)
> 
> What? This is a weird statement.
> So, you're saying that nobody has successfully written any threadsafe
> code, ever... we should stop trying, and we should admit that
> threadsafe queues and atomics, and mutexes and stuff all don't exist?
> 

Obviously I am not saying that.

>> without cooperation from both of them?
> 
> Perhaps this is the key to your statement?

Yes.

> Yes. 'cooperation from both of them' in this case means, they are both
> interacting with a threadsafe api, and they are blocked from accessing
> members, or any non-threadsafe api.
> ...

Yes. Your proposal only enforces this for the shared alias.

>>> Giving an unshared value to a function that
>>> even can handle shared values may create some overhead, but is
>>> indeed threadsave.
>>>
>>
>> Yes, if you give it to one function only, that is the case. However, as
>> you may know, concurrency means that there may be multiple functions
>> operating on the data _at the same time_. If one of them operates on the
>> data as if it was not shared, you will run into trouble.
> 
> Who's doing this,

Anyone, it really does not matter. One major point of the type system is 
to ensure that _all_ @safe code has defined behavior. You can convert 
between shared and unshared, just not in @safe code.

> and how?
> ...

They create a mutable instance of a class, they create a shared alias 
using one of your proposed holes, then send the shared alias to another 
thread, call some methods on it in both threads and get race conditions.

>> You are arguing as if there was either no concurrency or no mutable
>> aliasing.
> 
> If a class has no shared methods, there's no possibility for mutable aliasing.
> If the class has shared methods, then the class was carefully designed
> to be threadsafe.
> 

Not necessarily. Counterexample:

@safe:
class C{
     int x;
     void foo(){
         x+=1; // this can still race with atomicIncrement
     }
     void bar()shared{
         atomicIncrement(x); // presumably you want to allow this
     }
}

void main(){
     auto c=new C();
     shared s=c; // depending on your exact proposed rules, this step 
may be more cumbersome
     spawn!(()=>s.bar());
     s.foo(); // race
}

Now, if a class has only shared members, that is another story. In this 
case, all references should implicitly convert to shared. There's a DIP 
I meant to write about this. (For all qualifiers, not just shared).


More information about the Digitalmars-d mailing list