我经常听到有人说,为什么不能创建一个包含2000个线程的进程。
原因不是Windows中固有的任何特定限制。相反,程序员没有考虑每个线程使用的地址空间量。
线程由内核模式下的一些内存(内核堆栈和对象管理)和用户模式下的一些内存(线程环境块、线程本地存储等)及其堆栈组成。(如果你使用的是安腾系统,则为多个堆栈。)
通常,限制因素是线程堆栈大小。
请看下面的代码:
此程序通常会打印一个大约 2000 的线程数值。
为什么大概会是2000这个数呢?
因为链接器分配的默认堆栈大小为 1MB,而 2000 个堆栈乘以每个堆栈 1MB 等于大约 2GB,这是用户模式程序可用的地址空间量。
你可以尝试通过减小堆栈大小将更多线程压缩到进程中,这可以通过调整链接器选项或手动重写传递给 CreateThread 函数的堆栈大小来完成,如 MSDN 中所述的那样。
HANDLE h = CreateThread(NULL, 4096, ThreadProc, NULL,
STACK_SIZE_PARAM_IS_A_RESERVATION, &id);
通过这个修改,我能够创建大约 13000 个线程。虽然这肯定比 2000 个要好,但它没有达到 500,000 个线程的天真期望(线程在 2GB 地址空间中使用 4KB 的堆栈)。但是你忘记了另一个开销。地址空间分配粒度为 64KB,因此即使仅使用 4KB 的地址空间,每个线程的堆栈也会占用 64KB 的地址空间。另外,当然,你不能自由支配所有2GB的地址空间;有系统 DLL 和其他东西占用它。
但是,每当有人问”一个进程可以创建的最大线程数是多少?”时,就会提出真正的问题是”为什么你创建了这么多线程,以至于线程数量成为了一个问题?”
众所周知,”每个客户端一个线程”的模型,不会扩展到十几个客户端左右。如果要同时处理超过这么多的客户端,则应转到一个模型,而不是将线程专用于客户端,而是没每个客户端分配一个对象。(总有一天,我会思考线程和对象之间的二元性。Windows 提供 I/O 完成端口和线程池,以帮助你从基于线程的模型转换为基于工作项的模型。)
请注意,纤程(Fibers)在这里没有多大帮助,因为纤程有一个堆栈,而堆栈所需的地址空间几乎始终是限制因素。
总结
Topomel Box 在开发中也会频繁使用线程来提升用户体验,但一般都是在短时间内线程完成了它的工作后就退出了,这样不会造成系统资源耗尽的问题。
如果有一天,你真的需要问出”一个进程能创建多少个线程”这个问题时,你需要仔细想想:为什么需要创建这么多线程?
“有没有可能,哪里设计的不合理?”
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Does Windows have a limit of 2000 threads per process?》