shared - i need it to be useful

Steven Schveighoffer schveiguy at gmail.com
Thu Oct 18 19:06:12 UTC 2018


On 10/18/18 2:24 PM, Manu wrote:
> I understand your argument, and I used to think this too... but I
> concluded differently for 1 simple reason: usability.

You have not demonstrated why your proposal is usable, and the proposal 
to simply make shared not accessible while NOT introducing implicit 
conversion is somehow not usable.

I find quite the opposite -- the implicit conversion introduces more 
pitfalls and less guarantees from the compiler.

> I have demonstrated these usability considerations in production. I am
> confident it's the right balance.

Are these considerations the list below, or are they something else? If 
so, can you list them?

> I propose:
>   1. Normal people don't write thread-safety, a very small number of
> unusual people do this. I feel very good about biasing 100% of the
> cognitive load INSIDE the shared method. This means the expert, and
> ONLY the expert, must make decisions about thread-safety
> implementation.

Thread safety is not easy. But it's also not generic.

In terms of low-level things like atomics and lock-free implementations, 
those ARE generic and SHOULD only be written by experts. But other than 
that, you can't know how someone has designed all the conditions in 
their code.

For example, you can have an expert write mutex locks and semaphores. 
But they can't tell you the proper order to lock different objects to 
ensure there's no deadlock. That's application specific.

>   2. Implicit conversion allows users to safely interact with safe
> things without doing unsafe casts. I think it's a complete design fail
> if you expect any user anywhere to perform an unsafe cast to call a
> perfectly thread-safe function. The user might not properly understand
> their obligations.

I also do not expect anyone to perform unsafe casts in normal use. I 
expect them to use more generic well-written types in a shared-object 
library. Casting should be very rare.

>   3. The practical result of the above is, any complexity relating to
> safety is completely owned by the threadsafe author, and not cascaded
> to the user. You can't expect users to understand, and make correct
> decisions about threadsafety. Safety should be default position.

I think these are great rules, and none are broken by keeping the 
explicit cast requirement in place.

> I recognise the potential loss of an unsafe optimised thread-local path.
> 1. This truly isn't a big deal. If this is really hurting you, you
> will notice on the profiler, and deploy a thread-exclusive path
> assuming the context supports it.

This is a mischaracterization. The thread-local path is perfectly safe 
because only one thread can be accessing the data. That's why it's 
thread-local and not shared.

> 2. I will trade that for confidence in safe interaction every day of
> the week. Safety is the right default position here.

You can be confident that any shared data is properly synchronized via 
the API provided. No confidence should be lost here.

> 2. You just need to make the unsafe thread-exclusive variant explicit, eg:

It is explicit, the thread-exclusive variant is not marked shared, and 
cannot be called on data that is actually shared and needs synchronization.

> 
>> struct ThreadSafe
>> {
>>      private int x;
>>      void unsafeIncrement() // <- make it explicit
>>      {
>>         ++x; // User has asserted that no sharing is possible, no reason to use atomics
>>      }
>>      void increment() shared
>>      {
>>         atomicIncrement(&x); // object may be shared
>>      }
>> }

This is more design by convention.

> 
> I think this is quiet a reasonable and clearly documented compromise.
> I think absolutely-reliably-threadsafe-by-default is the right default
> position. And if you want to accept unsafe operations for optimsation
> circumstances, then you're welcome to deploy that in your code as you
> see fit.

All thread-local operations are thread-safe by default, because there 
can be only one thread using it. That is the beauty of the current 
regime, regardless of how broken shared is -- unshared is solid. We 
shouldn't want to break that guarantee.

> If the machinery is not a library for distribution and local to your
> application, and you know for certain that your context is such that
> thread-local and shared are mutually exclusive, then you're free to
> make the unshared overload not-threadsafe; you can do this because you
> know your application context.
> You just shouldn't make widely distributed tooling this way.

I can make widely distributed tooling that does both shared and unshared 
versions of the code, and ALL are thread safe. No choices are necessary, 
no compromise on performance, and no design by convention.

> I will indeed do this myself in some cases, because I know those facts
> about my application.
> But I wouldn't compromise the default design of shared for this
> optimisation potential... deliberately deployed optimisation is okay
> to be unsafe when taken in context.
> 

Except it's perfectly thread safe to use data without synchronization in 
one thread -- which is supported by having unshared data. Unshared means 
only one thread. In your proposal, anything can be seen from one or more 
threads.

-Steve


More information about the Digitalmars-d mailing list