为什么Windows 10在我的程序中启动额外线程?

bksxznpy  于 2023-01-06  发布在  Windows
关注(0)|答案(3)|浏览(374)

使用Visual Studio 2015,在新的空C++项目中,为控制台应用程序构建以下内容:

int main() {
    return 0;
}

在返回时设置断点并在调试器中启动程序。在Windows 7上,截至断点,此程序只有一个线程。但在Windows 10上,它有五个(!)线程:主线程和四个等待同步对象的“工作线程”。
谁在启动线程池(或者我如何知道)?

mzmfm0qo

mzmfm0qo1#

水晶球说Debug〉Windows〉Threads窗口在ntdll.dll!TppWorkerThread显示这些线程。请确保启用Microsoft Symbol Server以自己看到这一点,使用Tools〉Options〉Debugging〉Symbols。
这也发生在VS 2013中,所以它肯定不是由新的VS2015诊断功能引起的,@Adam的猜测不可能是正确的。
TppWorkerThread()是线程池线程的入口点,当我使用Debug〉New Breakpoint〉Function Breakpoint在这个函数上设置断点时,我很幸运地在第二个线程池线程开始执行时捕获了第一个线程池线程的堆栈跟踪:

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

很明显,加载器使用Windows 10上的线程池来加载DLL。这当然是新的:)在这一点上,主线程也在加载器中执行,并发工作。
所以Windows 10正在利用多核来更快地初始化进程。非常像是一个功能,而不是一个bug:)

f45qwnt8

f45qwnt82#

这是默认的线程池。https://learn.microsoft.com/en-us/windows/desktop/procthread/thread-pools
每个进程都有一个默认线程池。

koaltpgm

koaltpgm3#

这也引起了我的兴趣,所以我决定找到我个人的答案;正如另一张海报所说,这有点像"水晶球"的努力,但是...
可能的原因是其中一个线程名为:

  • 等待单个对象或
  • 等待多个对象

在最新版本的Windows中实现这一点似乎会产生一个线程池,以便于等待对象(不知道为什么)。
这也可能发生在main之前,因为您有一些代码会导致创建全局作用域对象,然后在您到达入口点之前启动代码(这甚至可能在Windows 10 SDK的一些标准库代码中)。
对于任何人想要找出自己的具体原因,你可以试试这个:

class RunBeforeMain
{
public:
    RunBeforeMain()
    {
        HMODULE hNtDll = (HMODULE)LoadLibrary(_T("ntdll.dll"));
        FARPROC lpNeeded = GetProcAddress(hNtDll,"NtWaitForMultipleObjects");
        DebugBreakPoint();
    }
};

RunBeforeMain go;

int CALLBACK WinMain(
  _In_ HINSTANCE hInstance,
  _In_ HINSTANCE hPrevInstance,
  _In_ LPSTR     lpCmdLine,
  _In_ int       nCmdShow
)
{
}

当你运行这个程序时,你会在lpNeeded中得到NtDll过程NtWaitForMultipleObjects的库加载位置,获取那个地址并将它粘贴到反汇编视图窗口中,然后在第一行放置一个断点。
现在继续运行您的解决方案。
几点注意事项:
1.我们不能有效地控制全局变量的初始化顺序,这就是为什么如果你有良好的编程意识,你会不惜一切代价避免它们(除非有一些特殊的需要)。由于这个事实,我们不能保证我们的全局变量会在任何其他全局变量导致额外线程之前触发。
1.虽然这是在main之前,任何库的DLL加载都会继续我们的任何调用,因此,可能已经太晚了(你可以使用像强制不自动加载库这样的技巧,但这远远超出了我在这里关心的意愿)。
希望这对某人有帮助:)

相关问题