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

Why does Windows 10 start extra threads in my program?

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

int main() {
    return 0;
}

在 return 上设置断点并在调试器中启动程序。 Windows7日,截至断点,本程序只有一个线程。但是在 Windows 10 上,它有五个(!)线程:主线程和四个 "worker threads" 等待同步对象。

谁在启动线程池(或者我如何知道)?

Crystal 球说 Debug > Windows > Threads window 在 ntdll.dll!TppWorkerThread 显示了这些线程。请务必启用 Microsoft 符号服务器以自己查看此内容,请使用“工具”>“选项”>“调试”>“符号”。

这也发生在 VS2013 中,所以这绝对不是由新的 VS2015 诊断功能引起的,@Adam 的猜测不可能是正确的。

TppWorkerThread() 是 thread-pool 线程的入口点。当我使用 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

很明显,加载程序正在使用 Windows10 上的线程池来加载 DLL。这当然是新的:)此时主线程也在加载程序中执行,并发工作。

所以 Windows 10 正在利用多核来更快地初始化进程。非常重要的功能,而不是错误 :)

这是默认的线程池。 https://docs.microsoft.com/en-us/windows/desktop/procthread/thread-pools

每个进程都有一个默认的线程池。

这也引起了我的兴趣,所以我决定找到我个人的答案;正如另一位海报所说,它有点像“crystal 球”,但是...

可能的原因是您的其中一个线程称为:

  • WaitForSingleObject 或
  • WaitForMultipleObjects

最新版本 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 过程 NtW​​aitForMultipleObjects 的库加载位置,获取该地址并将其粘贴到反汇编视图中 window 然后在第一个上放置一个断点线.

现在继续运行您的解决方案。

一些注意事项:

  1. 我们无法有效地控制全局变量的初始化顺序,这就是为什么如果你有良好的编码意识,你就会不惜一切代价避免使用它们(除非有特殊需要)。由于这个事实,我们不能保证我们的全局会在任何其他全局导致额外线程之前触发。
  2. 虽然这是在 main 之前,但任何库的 DLL 加载都会继续我们的任何调用,因此,可能已经太晚了(您可以使用 hack,例如强制不自动加载库,但这超出了我的水平愿意关心这里哈哈)。

希望这对某人有所帮助:)