线程数受操作系统和内存限制,不是 .NET 硬编码上限
.NET 本身不设固定线程数量上限,
ThreadPool默认最大线程数由
ThreadPool.GetMaxThreads返回,但这个值只是线程池的“软限制”,实际能创建多少个
Thread实例,取决于 Windows(或 Linux/macOS)能为该进程分配多少栈空间和内核对象。
每个线程默认占用 1MB 栈空间,内存很快见底
在 Windows 上,.NET 的
Thread默认栈大小是 1 MB(x64 进程),哪怕只调用
new Thread(() => {}).Start(),也会立即向进程地址空间申请这块保留内存(commit 按需)。32 位进程用户态地址空间仅约 2GB,理论上撑不过 2000 个线程;64 位进程虽大得多,但物理内存和页表开销会先成为瓶颈。常见现象是:创建几百个线程后,OutOfMemoryException或
Thread.Start()抛出
InvalidOperationException(内部触发
ERROR_NOT_ENOUGH_MEMORY)。
Thread构造时可通过
new Thread(..., stackSize)指定更小栈(如
256 * 1024),但低于 128KB 可能导致 StackOverflow 频繁创建/销毁线程比复用
ThreadPool或
Task开销大得多,且易触发 GC 压力 Windows 内核对每个进程的线程句柄数也有默认限制(通常 2000–4000),可通过
SetProcessInformation调整,但极少需要
别硬扛线程数,改用异步 I/O 和 Task 调度
真正需要“高并发”的场景(如万级连接的服务器),几乎从不依赖大量
Thread实例。正确做法是:用
async/await+
SocketAsyncEventArgs(或
System.Net.Sockets.Socket的异步方法),让单线程处理数百连接;CPU 密集任务则交由
Task.Run并依赖
ThreadPool自动伸缩(其最大线程数默认为
Environment.ProcessorCount * 100,但会按负载动态调整)。
var tasks = Enumerable.Range(0, 10000)
.Select(_ => Task.Run(() => HeavyComputation()))
.ToArray();
await Task.WhenAll(tasks); // 实际并发执行数远小于 10000,由线程池控制检查当前限制和实际使用量的方法
运行时可快速验证当前环境约束:
查线程池上限:ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads)查已启动线程数(粗略):
Process.GetCurrentProcess().Threads.Count(注意:包含调试器、GC、JIT 等后台线程) 观察私有字节(Private Bytes)增长:每新增一个默认栈线程,进程内存上升约 1MB(保留区)
真要突破几百线程,先确认是否误用了同步阻塞模型——这是绝大多数“需要更多线程”诉求的根源。
