Thread overview | |||||||
---|---|---|---|---|---|---|---|
|
October 06, 2018 Thread-safe attribution | ||||
---|---|---|---|---|
| ||||
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 Re: Thread-safe attribution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | 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 Re: Thread-safe attribution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Nicholas Wilson | 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 Re: Thread-safe attribution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | 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;
}
}
|
Copyright © 1999-2021 by the D Language Foundation