Thread overview
Thread-safe attribution
Oct 07, 2018
Manu
Oct 07, 2018
Nicholas Wilson
Oct 07, 2018
Manu
Oct 08, 2018
Stefan Koch
Oct 10, 2018
Kagamin
October 06, 2018
So I'm working on a SMT infrastructure, and expression of
thread-safety is a core design mechanic... but I'm really struggling
to express it in terms of the type system.
I figure I'll throw some design challenges out here and see if anyone
can offer some good ideas.

The thing I'm trying to model is an attribute along the lines of
`shared`, but actually useful ;)
I'll use the attribute `threadsafe` in place of `shared`, and see
where that goes.

Consider:
struct Bob
{
  int x;
  threadsafe Atomic!int y;

  void m1();
  void m2() threadsafe;;

  void overloaded();
  void overloaded() threadsafe;
}

void func(Bob x, threadsafe Bob y)
{
  x.x = 10; // fine
  x.y = 10; // fine
  x.m1(); // fine
  x.m2(); // fine
  x.overloaded(); // fine, use the un-threadsafe overload

  y.x = 10; // ERROR, a threadsafe reference can NOT modify an
un-threadsafe member
  y.y = 10; // fine
  x.m1(); // ERROR, method not threadsafe
  x.m2(); // fine
  x.overloaded(); // fine, use the threadsafe overload

  threadsafe Bob* p = &x; // can take threadsafe reference to
thread-local object
}

This is loosely what `shared` models, but there's a few differences:
1. thread-local can NOT promote to shared
2. shared `this` applies to members

For `shared` to be useful, it should be that a shared reference to something inhibits access to it's thread-local stuff. And in that world, then I believe that thread-local promotion to shared would work like const does.

I guess I'm wondering; should `shared` be transitive? Perhaps that's what's wrong with it...?
October 07, 2018
On Sunday, 7 October 2018 at 01:59:21 UTC, Manu wrote:
> So I'm working on a SMT infrastructure, and expression of
> thread-safety is a core design mechanic... but I'm really struggling
> to express it in terms of the type system.
> I figure I'll throw some design challenges out here and see if anyone
> can offer some good ideas.
>
> The thing I'm trying to model is an attribute along the lines of
> `shared`, but actually useful ;)
> I'll use the attribute `threadsafe` in place of `shared`, and see
> where that goes.
>
> Consider:
> struct Bob
> {
>   int x;
>   threadsafe Atomic!int y;
>
>   void m1();
>   void m2() threadsafe;;
>
>   void overloaded();
>   void overloaded() threadsafe;
> }
>
> void func( ref Bob x, ref threadsafe Bob y)
> {
>   x.x = 10; // fine
>   x.y = 10; // fine
>   x.m1(); // fine
>   x.m2(); // fine
>   x.overloaded(); // fine, use the un-threadsafe overload
>
>   y.x = 10; // ERROR, a threadsafe reference can NOT modify an
> un-threadsafe member
>   y.y = 10; // fine
>   x.m1(); // ERROR, method not threadsafe
>   x.m2(); // fine
>   x.overloaded(); // fine, use the threadsafe overload
>
>   threadsafe Bob* p = &x; // can take threadsafe reference to
> thread-local object
> }
>
> This is loosely what `shared` models, but there's a few differences:
> 1. thread-local can NOT promote to shared
> 2. shared `this` applies to members
>
> For `shared` to be useful, it should be that a shared reference to something inhibits access to it's thread-local stuff. And in that world, then I believe that thread-local promotion to shared would work like const does.
>
> I guess I'm wondering; should `shared` be transitive? Perhaps that's what's wrong with it...?

A delta comparison with shared

void func( ref Bob x, ref threadshared /* either shared or threadsafe*/ Bob y)
{
 // threadsafe / shared
  x.x = 10; // fine / fine
  x.y = 10; // fine / fine uses atomics
  x.m1(); // fine / fine
  x.m2(); // fine / error cannot call shared method on unshared object
  x.overloaded(); // fine, use the un-threadsafe overload / fine

  y.x = 10; // ERROR, a threadsafe reference can NOT modify an un-threadsafe member / error
  y.y = 10; // fine / fine (using atomics)
// Assuming these are supposed to be y not x
  y.m1(); // ERROR, method not threadsafe / error
  y.m2(); // fine / fine
  y.overloaded(); // fine, use the threadsafe overload / fine

  threadsafe Bob* p = &x; // can take threadsafe reference to thread-local object / error
}

Differences:
Can call threadsafe method on thread local / unshared
Can take threadsafe reference to thread-local object.

One thing that occurred to me is that _objects_ are shared, whereas _functions/methods_ (and their parameters) are thread safe .

Theadsafe is kind of like a const (as to mutable/immutable) to threading, a promise to behave correctly in the presence of threading. thread safe references therefore must not escape.

October 06, 2018
On Sat, Oct 6, 2018 at 7:40 PM Nicholas Wilson via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> [...]
>
> One thing that occurred to me is that _objects_ are shared, whereas _functions/methods_ (and their parameters) are thread safe .
>
> Theadsafe is kind of like a const (as to mutable/immutable) to threading, a promise to behave correctly in the presence of threading. thread safe references therefore must not escape.

Right, that's kinda what I want to model... but the more I think of it, the more I think that experience can fit into `shared`, because it's almost there, and the current incarnation of shared is objectively useless.

Consider shared as is today;
struct Bob
{
  int x;
  void f() shared
  {
    x = 10; // <- this compiles... WAT?!
  }
}

Right now, if you have a shared instance, you can read/write to the
members... and that makes *absolutely no sense* no matter how you look
at it.
There is no reality where you have a shared thing, and accessing
members un-controlled can be safe.

Conventional wisdom is that when you have a shared thing, and you want
to do stuff with it, you must acquire locks (or whatever) and case
shared away. That should apply to f() above.

struct Bob
{
  int x;
  void f() shared
  {
    auto lock = getLock();
    auto unshared = shared_cast(&this);
    unshared.x = 10; // <- this is now okay.
  }
}

If we made a change were `shared` lost the ability to access non-`shared` members, I don't think that would interfere with current or proposed uses of shared in any way whatsoever... and we would make shared useful in the process.
October 08, 2018
On Sunday, 7 October 2018 at 02:59:12 UTC, Manu wrote:
> On Sat, Oct 6, 2018 at 7:40 PM Nicholas Wilson via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>>
>> [...]
>>
>> One thing that occurred to me is that _objects_ are shared, whereas _functions/methods_ (and their parameters) are thread safe .
>>
>> Theadsafe is kind of like a const (as to mutable/immutable) to threading, a promise to behave correctly in the presence of threading. thread safe references therefore must not escape.
>
> Right, that's kinda what I want to model... but the more I think of it, the more I think that experience can fit into `shared`, because it's almost there, and the current incarnation of shared is objectively useless.
>
> Consider shared as is today;
> struct Bob
> {
>   int x;
>   void f() shared
>   {
>     x = 10; // <- this compiles... WAT?!
>   }
> }
>
> Right now, if you have a shared instance, you can read/write to the
> members... and that makes *absolutely no sense* no matter how you look
> at it.
> There is no reality where you have a shared thing, and accessing
> members un-controlled can be safe.
>
> Conventional wisdom is that when you have a shared thing, and you want
> to do stuff with it, you must acquire locks (or whatever) and case
> shared away. That should apply to f() above.
>
> struct Bob
> {
>   int x;
>   void f() shared
>   {
>     auto lock = getLock();
>     auto unshared = shared_cast(&this);
>     unshared.x = 10; // <- this is now okay.
>   }
> }
>
> If we made a change were `shared` lost the ability to access non-`shared` members, I don't think that would interfere with current or proposed uses of shared in any way whatsoever... and we would make shared useful in the process.

I think it should be more like this
shared struct Bob
{
    shared int x;
    void f() shared
    {
      Laquire:
      auto owned_bob = try_aquire(&this, pthread_self());
      // type will be the same as Bob* but without shared stripped from variables
      // and without any functions
      if (owned_bob is null)
      {
          __mmPause();
          goto Laquire;
      }
      *owned_bob.x = 10;
    }

}
October 10, 2018
struct Bob
{
  threadsafe Atomic!(string[string]) y;
}

void f(ref threadsafe Bob b)
{
  string[string] aa=b.y;
  aa["b"]="c";
}

Like this?