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

tsbockman thomas.bockman at gmail.com
Tue Jan 12 22:00:51 UTC 2021


On Tuesday, 12 January 2021 at 14:00:11 UTC, Steven Schveighoffer 
wrote:
> On 1/11/21 8:49 PM, tsbockman wrote:
>> However, this re-ordering IS permitted to freely alter the 
>> behavior of your code from the perspective of OTHER threads. A 
>> likely cause of your bug is that the write to db by the 
>> constructor's thread is being committed to memory after the 
>> read of db by the MessageService thread.
>
> I don't think this is valid.

You might be right, but your analysis below assumes the answers 
to a number of questions which aren't answered in the source code 
provided by the OP. Perhaps you are familiar with the 
implementations of the APIs in question, but I'm not and thought 
it unwise to assume too much, given that the whole reason we're 
having this discussion is that the code doesn't actually work...

Regardless, the simple way to find out if I'm on the right track 
or not is just to protect access to Foo's fields with a mutex and 
see if that fixes the problem. If it does, then either it's a 
memory ordering issue like I suggested (or a code gen bug), and 
the mutex can be replaced with something more efficient if 
necessary.

> 1. the compiler MUST NOT reorder the storage of db to after you 
> pass a delegate into an opaque function (array allocation).

Is the function actually opaque, though? If the source code is 
available to the compiler for inlining (or maybe if it's marked 
`pure`?) then reordering is still allowed.

> 2. The CPU is not going to reorder, because the memory 
> allocation is going to take a global lock anyway (mutex locks 
> should ensure memory consistency).

This is not a safe assumption. It is quite easy to design a 
thread-safe allocator that does not take a global lock for every 
allocation, and indeed *necessary* if you want it to scale well 
to heavy loads on high core count systems.

Even if that's how it works today, I wouldn't write code that 
depends on this behavior, unless the language standard formally 
guaranteed it, because someone will change it sooner or later as 
core counts continue to climb.

> I can't ever imagine creating a thread (which is likely what 
> MessageService ctor is doing) to not have a consistent memory 
> with the creating thread on construction.

It seems reasonable to assume that thread creation includes a 
write barrier somewhere, but what if MessageService is using an 
existing thread pool?

> The CPU would have to go out of its way to make it inconsistent.

No, there are many levels of caching involved in the system, most 
of which are not shared by all cores. The CPU has to go out of 
its way to make memory appear consistent between cores, and this 
is expensive enough that it doesn't do so by default. That's why 
atomics and memory barriers exist, to tell the CPU to go out of 
its way to make things consistent.

You often don't have to deal with these issues directly when 
using higher-level multi-threading APIs, but that's because they 
try to include the appropriate atomics/barriers internally, not 
because the CPU has to "go out of its way" to make things 
inconsistent.


More information about the Digitalmars-d-learn mailing list