0

Why does Windows 10 start extra threads in my program?

Lets see a simple code,

class AA
{
...
};

AA& getAA(){
  static AA aa;
  return aa;
}

class BB
{
  public:  BB() { getAA(); ... }
...
};
thread_local BB bb;

I added more information what I encounterd.

. With this simple code, I rewrite callstacks.

The exception is thrown occasionally at 0x00007FFB0B1272A6(ntdll.dll). Access violation writing location 0x000000024. The members of g_tss_mutex are zero except LockCount, which was 6, and LockSemaphore, which was 0xffffffffffffffff. The function is _Init_thread_lock() and it is calling EnterCriticalSection(&g_tss_mutex); Worker thread ntdll.dll thread throw this exception. call stack was.

ntdll.dll!00007ffb0b1272a6()
ntdll.dll!00007ffb0b13b5f6()
ntdll.dll!00007ffb0b13b440()
_init_thread_lock() 
_Init_thread_header(int *pOnce)
getAA()
BB::BB()
`dynamic initializer for 'bb'()

The call stack of the main thread was

_should_initialize_environment()
pre_c_initialization()
ucrtbased.dll!00007ffe9c1a4ab9
__scrt_common_main_seh()
__scrt_common_main()
WinMainCrtStartup()
....

Loader Threads of Windows 10 thread pool try to initialize thread_local bb. But if Loader threads call constructor of bb before the main thread of a process does not call _scrt_initialize_thread_safe_statics_platform_specific() to gerentee static magic. https://learn.microsoft.com/en-us/cpp/build/reference/zc-threadsafeinit-thread-safe-local-static-initialization?view=vs-2019

I met this situation. What can I do to handle this problem.

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • I couldn't understand the sentence "But if loader threads ..." -- can you try to clarify? – tenfour Mar 18 '20 at 09:57
  • Sorry, I don't understand this question. Could you try to rephrase it? – Asteroids With Wings Mar 18 '20 at 09:58
  • @tenfour LoaderThreads mean threads made by ntdll for loading library pararell. – Chanhyun Park Mar 18 '20 at 10:52
  • @AsteroidsWithWings sorry. To support thread safety of initializing static local variable, _scrt_initialize_thread_safe_statics_platform_specific() is called. So any initialization of static local variable (`aa` in `getAA()`) should happen after that function call. However, to initialize ntdll's worker threads, tls_init() function is called before that function is called. At this time, `__bb` is being initialized, as a result constructor of `aa` in``getAA()` is called. – Chanhyun Park Mar 18 '20 at 11:01
  • @walnut sorry about my mistake. I rewrote the code. `__aa` should be `__bb`, and `__bb` should be `aa` in `getAA()`. – Chanhyun Park Mar 18 '20 at 11:03
  • So you're suggesting that `static AA aa` in `getAA` is *not* initialized in a thread-safe manner? Can you provide some evidence as to how you came to that conclusion? – G.M. Mar 18 '20 at 12:01
  • @G.M. It is not my conclusion. It happens. But I failed to reproduce it with sample code. Exception Thrown at 0x0007FFB0B1272A6 (ntdll.dll) `EnterCriticalSection(&g_tss_mutex) in void __cdecl _Init_thread_lock()` in Worker Thread. ntdll.dll thread. In main thread. __local_stdio_printf_options.. is called. – Chanhyun Park Mar 18 '20 at 12:29
  • If there is no matter with MS's thread safe initialization of thread local variable, how come Exception is thrown at _init_thread_lock() ? – Chanhyun Park Mar 18 '20 at 12:42
  • @ChanhyunPark You can edit your question by clicking "edit" under it or by clicking [here](https://stackoverflow.com/posts/60736903/edit). If you made a mistake in writing the question or need to add information, please do so. – walnut Mar 18 '20 at 13:25
  • @walnut Thank alot.. I edited my questions. – Chanhyun Park Mar 18 '20 at 14:03
  • this is unrelated to loader threads. ntdll not call tls callbacks (and dll entry points) on loader thread. so here `BB::BB()` called on some another thread – RbMm Mar 21 '20 at 16:35
  • and this of course not ntdll thread pool or windows bug. this is c/c++ crt bug. if somebody create thread in process, before first thread finish crt initialization, this can be – RbMm Mar 21 '20 at 20:04

1 Answers1

0

Even if Windows Thread Pool would not spawn its own threads, there could always be guests in your process. Antivirus software, management software, shell extensions, accessibility software... or even malware.

The key problem here is that thread_local constructed as early as threads start.

You can help by making their constructor constexpr with only static initialization, and perform any dynamic stuff later.

Or wrap your thread_local objects with std::optional, and construct them when they are needed yourself. Or create your own wrapper for that.

I understand this all are workarounds, and the solution would be that MSVC would delay construction of thread local variable. But I'm satisfied with workarounds. Send feedback to MS if you are not.

Ultimately, by delaying thread_local object construction you also optimize: you don't construct the object for threads in which your code will not ever run.

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79