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

Why does Windows 10 start extra threads in my program?

提问人:Adrian McCarthy 提问时间:1/16/2016 更新时间:1/4/2023 访问量:13778

问:

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

int main() {
    return 0;
}

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

谁在启动线程池(或者我如何找到)?

C++ Windows 线程池

评论

0赞 Alexander Shishenko 1/16/2016
关于 Windows 和无限循环的笑话来了......
0赞 Jonathan Potter 1/16/2016
也许进程在 Windows 10 上默认获得线程池。
1赞 Ben Voigt 1/16/2016
我会先在 上放一个断点。请注意,使用 windbg 按名称放置断点很常见,而在 Visual Studio 调试器中,这是可能的,但需要学习一些不寻常的菜单命令。CreateThread
0赞 Christophe 1/16/2016
是您在 Visual Studio 中观察到的 trhads 吗?或者,当您直接从命令行运行代码时,您可以看到的线程(例如在ProcessExplorer中)?
1赞 Adrian McCarthy 1/16/2016
@Christophe:你是否建议 Visual Studio 调试器将线程池注入到所测试的进程中,但仅限于 Windows 10?

答:

44赞 Hans Passant 1/16/2016 #1

Crystal ball 表示“调试”> Windows > Threads 窗口在 中显示这些线程。请务必启用 Microsoft 符号服务器 若要自行查看此内容,请使用“工具”>“选项”>“调试>符号”。ntdll.dll!TppWorkerThread

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

TppWorkerThread() 是线程池线程的入口点。当我使用调试设置断点时> 新建断点>函数断点。当第二个线程池线程开始执行时,我很幸运地捕获了第一个线程池线程的堆栈跟踪:

    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 正在利用多个内核来更快地初始化进程。很大程度上是一个功能,而不是一个错误:)

评论

3赞 David Haim 1/16/2016
我的问题是:假设我已经分析了我的服务器,我看到 5 个线程带来了最大的性能。新版本的 Windows 是否可以自由地使用我已经创建的线程中的更多线程?我不是在要求在 Web 服务器上使用线程池,而是在这里提出的问题,即“我的程序中不需要的操作系统线程”
3赞 Harry Johnston 1/17/2016
请注意,Windows 有权根据需要创建任意数量的线程。如果你的程序依赖于没有不是你自己创建的线程,那就是一个错误。
14赞 Adrian McCarthy 3/8/2016
@Voo:使用原生代码的一大好处是不必为不使用的资源付费。如果我的应用程序不需要线程池,那么它仍然需要为四个线程的堆栈空间付费,这似乎很奇怪。如果我使用一个具有大型运行时系统的框架,我不会感到惊讶。但是,即使是最简单的程序现在也会启动多个线程,即使它从未使用过它们。
4赞 conio 9/29/2017
@Joshua:不,不是。它从来都不是。
6赞 Suma 8/5/2018
后来写了一篇文章引用了这个答案,并详细描述了加载器线程池:threatvector.cylance.com/en_us/home/...
4赞 Changming Sun 8/5/2018 #2

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

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

评论

7赞 Adrian McCarthy 8/6/2018
当然,但是,在 Windows 10 之前,如果程序不使用线程池,则不会创建额外的线程。从 Windows 10 开始,所有程序现在都需要支付启动多个线程(计算和内存)的成本,即使它们不需要它们。
2赞 Harry Johnston 8/13/2018
如果确实需要,可以禁用此功能,请参阅已接受答案评论中的 Suma 链接。但成本是最小的(一些额外的线程在前三十秒处于等待状态),所以我怀疑在大多数情况下,更快的加载时间将弥补这一点。
0赞 KVKConsultancy 7/26/2020 #3

这也引起了我的兴趣,所以我决定找到我个人的答案。正如另一张海报所说,这有点像“水晶球”的努力,但是......

可能的原因是您的线程之一,称为:

  • WaitForSingleObject 或
  • WaitForMultipleObjects

在最新版本的 Windows 中实现这一点似乎会生成一个线程池,以方便等待对象(不知道为什么)。

这也可能在主代码之前发生,因为你有一些代码会导致创建一个全局范围的对象,然后在你到达入口点之前启动代码(这甚至可能在 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. 我们无法有效地控制全局变量的初始化顺序,这就是为什么如果你有很好的编码能力,你会不惜一切代价避免它们(除非有一些特殊需要)。由于这个事实,我们不能保证我们的全局将在任何其他全局导致其他线程之前触发。
  2. 虽然这是在main之前,但任何库的DLL加载都会继续我们的任何调用,因此,可能已经太晚了(您可以使用诸如强制不自动加载库之类的技巧,但这远远超出了我在这里关心的意愿水平,哈哈)。

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