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