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