shared - i need it to be useful

Simen Kjærås simen.kjaras at gmail.com
Thu Oct 18 21:55:52 UTC 2018


On Thursday, 18 October 2018 at 14:19:41 UTC, Steven 
Schveighoffer wrote:
> On 10/18/18 10:11 AM, Simen Kjærås wrote:
>> On Thursday, 18 October 2018 at 13:35:22 UTC, Steven 
>> Schveighoffer wrote:
>>> struct ThreadSafe
>>> {
>>>    private int x;
>>>    void increment()
>>>    {
>>>       ++x; // I know this is not shared, so no reason to use 
>>> atomics
>>>    }
>>>    void increment() shared
>>>    {
>>>       atomicIncrement(&x); // use atomics, to avoid races
>>>    }
>>> }
>> 
>> But this isn't thread-safe, for the exact reasons described 
>> elsewhere in this thread (and in fact, incorrectly leveled at 
>> Manu's proposal). Someone could write this code:
>> 
>> void foo() {
>>      ThreadSafe* a = new ThreadSafe();
>>      shareAllOver(a);
>
> Error: cannot call function shareAllOver(shared(ThreadSafe) *) 
> with type ThreadSafe *

Sorry, typo. Should of course have been 
shareAllOver(cast(shared)a);

>>      a.increment(); // unsafe, non-shared method call
>> }
>> 
>> When a.increment() is being called, you have no idea if anyone 
>> else is using the shared interface.
>
> I do, because unless you have cast the type to shared, I'm 
> certain there is only thread-local aliasing to it.



>
>> This is one of the issues that MP (Manu's Proposal) tries to 
>> deal with. Under MP, your code would *not* be considered 
>> thread-safe, because the non-shared portion may interfere with 
>> the shared portion. You'd need to write two types:
>> 
>> struct ThreadSafe {
>>      private int x;
>>      void increment() shared {
>>          atomicIncrement(&x);
>>      }
>> }
>> 
>> struct NotThreadSafe {
>>      private int x;
>>      void increment() {
>>          ++x;
>>      }
>> }
>> 
>> These two are different types with different semantics, and 
>> forcing them both into the same struct is an abomination.
>
> Why? What if I wanted to have an object that is local for a 
> while, but then I want it to be shared (and I ensure carefully 
> when I cast to shared that there are no other aliases to that)?
>
>> In your case, the user of your type will need to ensure 
>> thread-safety.
>
> No, the contract the type provides is: if you DON'T cast 
> unshared to shared or vice versa, the type is thread-safe.
>
> If you DO cast unshared to shared, then the type is thread-safe 
> as long as you no longer use the unshared reference.
>
> This is EXACTLY how immutable works.

Yes, and that means the user of the type will need to follow 
these rules to ensure thread-safety. Which is what I said. Under 
MP, it's simply safe.


>> You may not have any control over how he's doing things, while 
>> you *do* control the code in your own type (and module, since 
>> that also affects things). Under MP, the type is what needs to 
>> be thread-safe, and once it is, the chance of a user mucking 
>> things up is much lower.
>
> Under MP, the type is DEFENSIVELY thread-safe, locking or using 
> atomics unnecessarily when it's thread-local.

Yes, because safety >> efficiency. There's nothing stopping you 
from making fast, unsafe functions under MP. Call them 
unsafe_<foo> to make them easy to grep for. Since only the type 
implementer, not the users, write this code, it's not an undue 
burden.

One of the greatest benefits of MP is that all the potential 
problem points are in one place. In a large codebase with 
multiple developers, anyone anywhere could be using a dangling 
unshared reference under the current schema. Under MP, that's 
confined to the type, not its uses.

--
   Simen





More information about the Digitalmars-d mailing list