shared - i need it to be useful

Steven Schveighoffer schveiguy at gmail.com
Tue Oct 16 21:19:26 UTC 2018


On 10/16/18 4:26 PM, Manu wrote:
> On Tue, Oct 16, 2018 at 11:30 AM Steven Schveighoffer via
> Digitalmars-d <digitalmars-d at puremagic.com> wrote:
>>
>> On 10/16/18 2:10 PM, Manu wrote:
>>> On Tue, Oct 16, 2018 at 6:35 AM Steven Schveighoffer via Digitalmars-d
>>> <digitalmars-d at puremagic.com> wrote:
>>>>
>>>> On 10/16/18 9:25 AM, Steven Schveighoffer wrote:
>>>>> On 10/15/18 2:46 PM, Manu wrote:
>>>>
>>>>>>>   From there, it opens up another critical opportunity; T* -> shared(T)*
>>>>>> promotion.
>>>>>> Const would be useless without T* -> const(T)* promotion. Shared
>>>>>> suffers a similar problem.
>>>>>> If you write a lock-free queue for instance, and all the methods are
>>>>>> `shared` (ie, threadsafe), then under the current rules, you can't
>>>>>> interact with the object when it's not shared, and that's fairly
>>>>>> useless.
>>>>>>
>>>>
>>>> Oh, I didn't see this part. Completely agree with Timon on this, no
>>>> implicit conversions should be allowed.
>>>
>>> Why?
>>
>> int x;
>>
>> shared int *p = &x; // allow implicit conversion, currently error
>>
>> passToOtherThread(p);
>>
>> useHeavily(&x);
> 
> What does this mean? It can't do anything... that's the whole point here.
> I think I'm struggling here with people bringing presumptions to the
> thread. You need to assume the rules I define in the OP for the
> experiment to work.

OK, I wrote a whole big response to this, and I went and re-quoted the 
above, and now I think I understand what the point of your statement is.

I'll first say that if you don't want to allow implicit casting of 
shared to mutable, then you can't allow implicit casting from mutable to 
shared. Because it's mutable, races can happen.

There is in fact, no difference between:

int *p;
shared int *p2 = p;
int *p3 = cast(int*)p2;

and this:

int *p;
shared int *p2 = p;
int *p3 = p;

So really, the effort to prevent the reverse cast is defeated by 
allowing the implicit cast.

There is a reason we disallow assigning from mutable to immutable 
without a cast. Yet, it is done in many cases, because you are sometimes 
building an immutable object with mutable pieces, and want to cast the 
final result.

In this case, it's ON YOU to make sure it's correct, and the traditional 
mechanism for the compiler giving you the responsibility is to require a 
cast.

-----

OK, so here is where I think I misunderstood your point. When you said a 
lock-free queue would be unusable if it wasn't shared, I thought you 
meant it would be unusable if we didn't allow the implicit cast. But I 
realize now, you meant you should be able to use a lock-free queue 
without it being actually shared anywhere.

What I say to this is that it doesn't need to be usable. I don't care to 
use a lock-free queue in a thread-local capacity. I'll just use a normal 
queue, which is easy to implement, and doesn't have to worry about race 
conditions or using atomics. A lock free queue is a special thing, very 
difficult to get right, and only really necessary if you are going to 
share it. And used for performance reasons!

Why would I want to incur performance penalties when using a lock-free 
queue in an unshared mode? I would actually expect 2 separate 
implementations of the primitives, one for shared one for unshared.

What about primitives that would be implemented the same? In that case, 
the shared method becomes:

auto method() { return (cast(Queue*)&this).method; }

Is this "unusable"? Without a way to say, you can call this on shared or 
unshared instances, then we need to do it this way.

But I would trust the queue to handle this properly depending on whether 
it was typed shared or not.

-Steve


More information about the Digitalmars-d mailing list