Bug 17476 – Static fields don't seem to be reliably initialized when using parallel()

Status
RESOLVED
Resolution
INVALID
Severity
major
Priority
P1
Component
phobos
Product
D
Version
D2
Platform
x86_64
OS
Linux
Creation time
2017-06-07T11:47:00Z
Last change time
2017-06-07T17:15:01Z
Assigned to
nobody
Creator
andrej.mitrovich

Comments

Comment #0 by andrej.mitrovich — 2017-06-07T11:47:06Z
``` import std.stdio; import std.parallelism; struct Params { static string path = "/some/string/initializer"; } void main() { Params.path = "foobar"; foreach (_; parallel([1, 2, 3, 4])) { writefln("%s", Params.path); } } ``` Run this a few times, and each time the results will be wildly different: ``` ~/dev/d test.d * $ dmd -run test.d foobar /some/string/initializer /some/string/initializer /some/string/initializer ~/dev/d test.d * $ dmd -run test.d /some/string/initializer foobar /some/string/initializer /some/string/initializer ~/dev/d test.d * $ dmd -run test.d foobar foobar /some/string/initializer /some/string/initializer ```
Comment #1 by dfj1esp02 — 2017-06-07T12:13:28Z
In the first thread it will be "foobar", in other threads it will be "/some/string/initializer", the exact sequence may depend on how parallel selects threads.
Comment #2 by uplink.coder — 2017-06-07T12:18:21Z
make path shared.
Comment #3 by dlang-bugzilla — 2017-06-07T16:23:06Z
No bug here. path in a TLS variable, so there is one instance per thread. In main, you set the TLS instance of path corresponding to the main thread to "foobar". The "parallel" loop body will use the current thread's TLS instance, but because the order of execution of threads is of course undefined, you get inconsistent results. I'm not sure what you expect - that the order of thread execution is deterministic? This is of course impossible. Or do you want one instance of "path" per the entire program, shared across threads? Then move it out of the TLS into the stack or data segment (but watch out for data races).
Comment #4 by mathias.lang — 2017-06-07T16:31:17Z
> In main, you set the TLS instance of path corresponding to the main thread to "foobar". The "parallel" loop body will use the current thread's TLS instance, but because the order of execution of threads is of course undefined, you get inconsistent results. So you can implicitly capture TLS variables and use them from other thread ? Doesn't that completely defeat the purpose of thread locality ?
Comment #5 by dlang-bugzilla — 2017-06-07T16:33:33Z
(In reply to Mathias Lang from comment #4) > So you can implicitly capture TLS variables and use them from other thread ? > Doesn't that completely defeat the purpose of thread locality ? I don't understand your question. If you want to always refer to the main thread's TLS instance of "path" inside the parallel foreach body, you can explicitly save a copy or a pointer to it outside the foreach body on main's stack, then refer to it inside the foreach body.
Comment #6 by andrej.mitrovich — 2017-06-07T16:48:58Z
> In main, you set the TLS instance of path corresponding to the main thread to "foobar". The "parallel" loop body will use the current thread's TLS instance, but because the order of execution of threads is of course undefined, you get inconsistent results. I was initially confused not by the order of things, but by the multiple printouts of "foobar". However this explains everything: ----- import std.concurrency; import std.stdio; import std.parallelism; struct Params { static string path = "/some/string/initializer"; } void main() { Params.path = "foobar"; foreach (_; parallel([1, 2, 3, 4])) { writefln("%s %s", thisTid(), Params.path); } } ----- Tid(7fe4c452e800) foobar Tid(7fe4c452e800) foobar Tid(7fe4c452e900) /some/string/initializer Tid(7fe4c452ea00) /some/string/initializer It's a non-issue, we were just puzzled at first. :)
Comment #7 by mathias.lang — 2017-06-07T17:11:45Z
When a thread runs I would expect the following (simplified) course of action: - Initialize the TLS data - Do the writeln - Exit Given the original code, I would then expect the code to print 3 times "/some/string/initializer" and one time "foobar". I would not expect any order. Which means the first 2 runs give a perfectly valid output. However, the 3rd run outputs "/some/string/initializer" twice, and "foobar" twice. How can "foobar" be printed twice if it's only set for one thread ? If the delegate generated from the `foreach` body would capture the static instance of the main thread, we would get 4 "foobar". but we don't.
Comment #8 by dlang-bugzilla — 2017-06-07T17:15:01Z
(In reply to Mathias Lang from comment #7) > How can > "foobar" be printed twice if it's only set for one thread ? Because both times "foobar" is written are done from the same (main) thread. parallel() can (and does) reuse threads.