Why Does Windows 10 Start Extra Threads in My Program

Why does Windows 10 start extra threads in my program?

Crystal ball says that the Debug > Windows > Threads window shows these threads at ntdll.dll!TppWorkerThread. Be sure to enable the Microsoft Symbol Server to see this yourself, use Tools > Options > Debugging > Symbols.

This also happens in VS2013 so it is most definitely not caused by the new VS2015 diagnostic features, @Adam's guess cannot be correct.

TppWorkerThread() is the entrypoint for a thread-pool thread. When I set a breakpoint with Debug > New Breakpoint > Function Breakpoint on this function. I got lucky to capture this stack trace for the 1st threadpool thread when the 2nd threadpool thread started executing:

    ntdll.dll!_NtOpenFile@24()  Unknown
ntdll.dll!LdrpMapDllNtFileName() Unknown
ntdll.dll!LdrpMapDllSearchPath() Unknown
ntdll.dll!LdrpProcessWork() Unknown
ntdll.dll!_LdrpWorkCallback@12() Unknown
ntdll.dll!TppWorkpExecuteCallback() Unknown
ntdll.dll!TppWorkerThread() Unknown
kernel32.dll!@BaseThreadInitThunk@12() Unknown
ntdll.dll!__RtlUserThreadStart() Unknown
> ntdll.dll!__RtlUserThreadStart@8() Unknown

Clearly the loader is using the threadpool on Windows 10 to load DLLs. That's certainly new :) At this point the main thread is also executing in the loader, concurrency at work.

So Windows 10 is taking advantage of multiple cores to get the process initialized faster. Very much a feature, not a bug :)

why a c++ application has three threads started by default?

ntdll.dll threads are in fact operating system threads started from library you link against. All your code is running on thread 16472.

Usually these are threadpool workers to load other dynamic libraries for your app. Windows 10 is optimizing process startup by utilizing multiple CPU cores to initialize faster.

10 threads in a single program or 1 thread program ran 10 times (C++)?

Threads are slightly lighter weight than processes as there are many things a process gets it's own copy of. Especially when you compare the time it takes to start a new thread, vs starting a new process (from scratch, fork where available also avoids a lot of costs). Although in either case you can generally get even better performance using a worker pool where possible rather than starting and stopping fresh processes/threads.

The other major difference is that threads by default all share the same memory while processes get their own and need to communicate through more explicit means (which may include blocks of shared memory). This might make it easier for a threaded solution to avoid copying data, but this is also one of the dangers of multithreaded programming when care is not taken in how they use the shared memory/objects.

Also there may be API's that are more orientated around a single process. For example on Windows there is IO Completion Ports which basically works on the idea of having many in-progress IO operations for different files, sockets, etc. with multiple threads (but generally far less than the number of files/sockets) handling the results as they become available through a GetQueuedCompletionStatus loop.

Multiple windows with seperate threads stop working since Windows 10 20H1

The following sample works for me without using threading pool:

MyTestMultiWindow.cpp

// MyTestMultiWindow.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "MyTestMultiWindow.h"
#include "windows.h"
#include "time.h"

#define MAX_LOADSTRING 100
#define MAX_THREADS 4

#define WC_MAINWINDOW TEXT("MainWindowClass")
#define WC_CHILDWINDOW TEXT("ChildWindowClass")
#define MAIN_TITLE TEXT("Main Window")
#define CHILD_TITLE TEXT("Child Window")
#define Random10() (int)((double) rand() / (double)RAND_MAX * 10)

// Global Variables:
HINSTANCE hInst; // current instance
WCHAR szTitle[MAX_LOADSTRING]; // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name

HWND hWndMain = NULL;
HANDLE hThreads[MAX_THREADS];
DWORD dwThreadId[MAX_THREADS];
HWND hwndThreads[MAX_THREADS];

DWORD dwCount = 1;

// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

DWORD WINAPI ThreadMainChild(void*);
LRESULT CALLBACK WndProcChild(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

// TODO: Place code here.

// Initialize global strings
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_MYTESTMULTIWINDOW, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);

// Perform application initialization:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}

HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MYTESTMULTIWINDOW));

MSG msg;

// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return (int)msg.wParam;
}

//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYTESTMULTIWINDOW));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MYTESTMULTIWINDOW);
wcex.lpszClassName = WC_MAINWINDOW;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

RegisterClassExW(&wcex);

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProcChild;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYTESTMULTIWINDOW));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MYTESTMULTIWINDOW);
wcex.lpszClassName = WC_CHILDWINDOW;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

return RegisterClassExW(&wcex);
}

//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable

HWND hWnd = CreateWindowW(WC_MAINWINDOW, MAIN_TITLE, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

if (!hWnd)
{
return FALSE;
}

hWndMain = hWnd;

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

return TRUE;
}

//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
int count = 0;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
srand((unsigned)time(NULL));
SetTimer(hWnd, 10, 1000, NULL);
for (int i = 0; i < MAX_THREADS; i++)
{
hThreads[i] = CreateThread(NULL, 0, ThreadMainChild, (LPVOID)i, CREATE_SUSPENDED, &dwThreadId[i]);
}
break;
case WM_TIMER:
if (count > 3)
{
KillTimer(hWnd, 10);
}
else
{
ResumeThread(hThreads[count]);
count++;
}
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;

case WM_SYSCOMMAND:
if (wParam == SC_MINIMIZE)
{
OutputDebugString(L"SC_MINIMIZE triggered.\n");
}
else if (wParam == SC_RESTORE)
{
OutputDebugString(L"SC_RESTORE triggered.\n");
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

DWORD WINAPI ThreadMainChild(void* lpParameter)
{
HWND hWndChild;
DWORD dwChildId = (DWORD)lpParameter;

while (1)
{
hWndChild = CreateWindow(WC_CHILDWINDOW, CHILD_TITLE, WS_CHILD | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
0, 0, 0, 0, hWndMain, (HMENU)(lpParameter), hInst, NULL);

if (!hWndChild)
{
DWORD dwGLE = GetLastError();
return 0;
}
else
{
hwndThreads[dwChildId] = hWndChild;
}

RECT rcMainArea;
GetClientRect(hWndMain, &rcMainArea);

int x, y;
x = ((rcMainArea.right - rcMainArea.left) / 2) * (((DWORD)lpParameter & 0x02) ? 1 : 0);
y = ((rcMainArea.bottom - rcMainArea.top) / 2) * (((DWORD)lpParameter & 0x01) ? 1 : 0);

SetWindowPos(hWndChild, NULL, x, y, (rcMainArea.right - rcMainArea.left) / 2, (rcMainArea.bottom - rcMainArea.top) / 2, SWP_NOZORDER | SWP_SHOWWINDOW);

ShowWindow(hWndChild, SW_SHOW);
UpdateWindow(hWndChild);

MSG msg;

// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Sleep(500);
}

return 0;
}

LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
SetTimer(hWnd, 11, 1000, NULL);
break;

case WM_TIMER:
if (wParam == 12)
{
}
else if (wParam == 11)
{
if (Random10() >= 8)
{
KillTimer(hWnd, 11);
DestroyWindow(hWnd);
for (int i = 0; i < MAX_THREADS; i++)
{
if (hwndThreads[i] == hWnd)
hwndThreads[i] = NULL;
}
}
else
{
SYSTEMTIME stTime;
TCHAR sTime[32];
GetLocalTime(&stTime);
wsprintf(sTime, TEXT("%02d:%02d:%02d"), stTime.wHour, stTime.wMinute, stTime.wSecond);

HDC hdc = GetDC(hWnd);
TextOut(hdc, 30, 30, sTime, lstrlen(sTime));
ReleaseDC(hWnd, hdc);

SetWindowText(hWnd, sTime);
}
}
break;

case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_EXIT:
DestroyWindow(hWnd);
for (int i = 0; i < MAX_THREADS; i++)
{
if (hwndThreads[i] == hWnd)
hwndThreads[i] = NULL;
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);

SYSTEMTIME stTime;
TCHAR sTime[32];
GetLocalTime(&stTime);
wsprintf(sTime, TEXT("%02d:%02d:%02d"), stTime.wHour, stTime.wMinute, stTime.wSecond);

TextOut(hdc, 30, 30, sTime, lstrlen(sTime));
ReleaseDC(hWnd, hdc);

SetWindowText(hWnd, sTime);

EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

How to access the default thread pool in C++ windows 10 application?

std::async uses the default thread pool on Windows, so you might like to use that.

More details here and here.



Related Topics



Leave a reply



Submit