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

Reply via email to