The only interesting thing I got out of exploring this a bit is that MSVC operates differently than clang/gcc.
Here is MSVC's output on the latest compiler:
Thread 34316 having counter value 1
Thread 34312 having counter value 1
Thread 34320 having counter value 1
Thread 34328 having counter value 1
Thread 34324 having counter value 1
Thread 34332 having counter value 1
Thread 34336 having counter value 1
Thread 34340 having counter value 1
Thread 34344 having counter value 1
Thread 34348 having counter value 1
Thread 29300 having counter value 0
c:\dv\TestCpp\out\build\x64-Debug (default)\TestCpp.exe (process 27748) exited with code 0.
Notice: "Thread 29300 having counter value 0". This is the main thread. Since doWork() was never called in the main thread the counter value is 0. This is working as I would expect things to work. "c" is a global Counter object - it should be created in all threads, including the main thread.
However, when I run (and godbolt doesn't seem to like to run multithreaded programs, AFAICT) under clang and gcc using rextester.com I get:
Thread 140199006193408 having counter value 1
Thread 140198906181376 having counter value 1
Thread 140198806169344 having counter value 1
Thread 140198706157312 having counter value 1
Thread 140199106205440 having counter value 1
Thread 140199206217472 having counter value 1
Thread 140199306229504 having counter value 1
Thread 140199406241536 having counter value 1
Thread 140199506253568 having counter value 1
Thread 140199606265600 having counter value 1
Notice: No main thread with the counter value of 0. The "c" Counter object was never created and destroyed in the main thread under gcc and clang however it was created and destroyed in the main thread under MSVC.
I believe that your personal bug is that you are using an old version of a compiler that has a bug in it. I couldn't reproduce your bug no matter which latest version of the various types of compilers that I used, but I am going to give a bug to the MSVC team because this is Standard C++ and one figures that it should produce the same results regardless of the compiler and it doesn't.
It seems (examining https://en.cppreference.com/w/cpp/language/storage_duration) that MSVC may be doing it wrong as the thread_local "Counter c" object was never explicitly accessed in the main thread and yet the object was created and destroyed.
We'll see what they say - I have a few other bugs pending to them...
Further research: The cpp reference article seems to indicate that since this is a "pure global" - i.e. it isn't a local static thread local object like:
Foo &
GetFooThreadSingleton()
{
static thread_local Foo foo;
return foo;
}
then they way I read the cppreference article is: it's a global like any other global, in which case MSVC is doing it correctly.
Someone please correct me if I am wrong. Thanks.
Also without running it natively in Ubuntu inside of gdb and checking I cannot be sure that the global wasn't actually created and destroyed in the main thread under gcc/clang because it could be that we just missed that output for some reason. I'll admit this seems unlikely. I'll try this later because this issue has intrigued me a bit.
I ran it natively in Ubuntu and got what I expected: The global "Counter c" is only created and destroyed if it is accessed in the main thread. So my thought is that this is a bug in gcc/clang at this point.