Member variables in method are null when called as delegate from thread

Steven Schveighoffer schveiguy at gmail.com
Mon Jan 11 16:10:49 UTC 2021


On 1/11/21 10:42 AM, Arafel wrote:
> On 11/1/21 14:42, Steven Schveighoffer wrote:
>>
>> That isn't exactly true. Member variables are members of the object. 
>> If the object is shared, the member variables are shared. If the 
>> object is local the variables are local.
>>
>> Thread local really only applies to *static* variables, such as 
>> globals or members declared static. If that were the case, yes, the 
>> other thread would not see the object.
>>
>> I did not respond to the OP because I also don't know why it wouldn't 
>> work. But I also don't know what all the code is doing.
>>
> 
> Out of curiosity, what happens with members that are declared `shared` 
> in a non-shared object?

A shared member is a sharable member of the class. It does not put the 
item in global storage.

There are some... odd rules.

struct S
{
    static int a; // TLS
    shared static int b; // shared data storage
    shared int c; // local variable, but its type is shared(int)
    immutable int d; // local immutable variable, settable only in 
constructor
    immutable int e = 5; // stored in data segment, not per instance!
    __gshared int f; // stored in global segment, typed as int, not 
shared(int)
}

> I thought that declaring an object `shared` "promotes" its members, but 
> that it wasn't strictly needed for a non-shared object to have shared 
> members, but I might be wrong here.

There are 2 different things here -- storage and type. shared as a 
storage class (i.e. without the parentheses) means 2 things:

1. for variables that are declared to be globals (either static or at 
module level), shared puts it in the shared data segment vs. thread 
local storage. For variables in all other declaration contexts, they are 
just stored where declared (either inside the instance or on the stack 
or whatever).
2. The type is modified to shared(T) instead of T.

> ```
> struct S {}
> 
> class A {
>      S s1;
>      shared S s2;
> }
> 
> void main() {
>      A a1 = new A();
>      pragma(msg, typeof(a1.s1)); // S
>      pragma(msg, typeof(a1.s2)); // shared(S)
>      shared A a2 = new shared A();
>      pragma(msg, typeof(a2.s1)); // shared(S)
>      pragma(msg, typeof(a2.s2)); // shared(S)
> }
> ```
> 
> https://run.dlang.io/is/skCfvE
> 
> Of course I don't know the practical differences in the actual 
> accessibility of the different members beyond the type system.

In the type system, shared basically means "other threads may have 
access". Right now, shared is kind of useless, because nothing is truly 
enforced except implicit conversions to/from shared are disallowed. In 
the future, shared data will be REQUIRED to be cast to unshared for usage.

> 
> Is there any way to check if a pointer is actually TLS or global 
> storage? If `a1.s2` can't be properly accessed from different threads, 
> I'd consider that a big bug in the `shared` implementation.

You are misunderstanding, a1 is stored on the heap, a2 is stored on the 
heap. In both cases a1.s2 and a2.s2 are stored in the object that is on 
the heap. The *type* being shared means you can pass its address to 
another thread (and semantically, shared means "another thread may be 
using this").

In order to ask the compiler to stick it in TLS or global shared 
storage, you have to mark it `static`.

-Steve


More information about the Digitalmars-d-learn mailing list