shared - i need it to be useful

Steven Schveighoffer schveiguy at gmail.com
Thu Oct 18 13:35:22 UTC 2018


On 10/17/18 10:26 PM, Manu wrote:
> On Wed, Oct 17, 2018 at 6:50 PM Steven Schveighoffer via Digitalmars-d
>>
>> The implicit cast means that you have to look at more than just your
>> method. You have to look at the entire module, and figure out all the
>> interactions, to see if the thread safe method actually is thread safe.
>> That's programming by convention, and fully trusting the programmer.
> 
> I don't understand... how can the outer context affect the
> threadsafety of a properly encapsulated thing?

[snip]

> You need to take it for an intellectual spin. Show me how it's corrupt
> rather than just presenting discomfort with the idea in theory.
> You're addicted to some concepts that you've carried around for a long
> time. There is no value in requiring casts, they're just a funky
> smell, and force the user to perform potentially unsafe manual
> conversions, or interactions that they don't understand.

For example (your example):

struct NotThreadsafe
{
   private int x;
   void local()
   {
     ++x; // <- invalidates the method below, you violate the other
function's `shared` promise
   }
   void notThreadsafe() shared
   {
     atomicIncrement(&x);
   }
}

First, note the comment. I can't look ONLY at the implementation of 
"notThreadSafe" (assuming the function name is less of a giveaway) in 
order to guarantee that it's actually thread safe. I have to look at the 
WHOLE MODULE. Anything could potentially do what local() does. I added 
private to x to at least give the appearance of thread safety.

But on top of that, if I can't implicitly cast mutable to shared, then 
this ACTUALLY IS thread safe, as long as all the casting in the module 
is sound (easy to search and verify), and hopefully all the casting is 
encapsulated in primitives like you have written. Because someone on the 
outside would have to cast a mutable item into a shared item, and this 
puts the responsibility on them to make sure it works.

I'm ALL FOR having shared be completely unusable as-is unless you cast 
(thanks for confirming what I suspected in your last post). It's the 
implicit casting which I think makes things way more difficult, and 
completely undercuts the utility of the compiler's mechanical checking.

And on top of that, I WANT that implementation. If I know something is 
not shared, why would I ever want to use atomics on it? I don't like 
needlessly throwing away performance. This is how I would write it:

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
    }
}

The beauty of shared not being implicitly castable, is it allows you to 
focus on the implementation at hand, with the knowledge that nothing 
else can meddle with it. The goal of mechanical checking should be to 
narrow the focus of what needs to be proven correct.

-Steve


More information about the Digitalmars-d mailing list