.NET 线程池 工作线程 扩容 + 空闲 + 回收 原理

0. 先明确 3 个线程池常量

这些是底层硬编码规则,所有逻辑都基于此:

  1. 最小工作线程数(MinThreads)

    • 默认 = CPU 核心数
    • 线程数 ≤ 最小值:立即创建、永不回收
  2. 最大工作线程数(MaxThreads)

    • 默认 = 32767(很大,基本不会触达)
    • 达到上限后,不再创建新线程,任务排队
  3. 两个关键时间

    • 扩容节流时间 :超过最小线程后,每 500ms 最多创建 1 个新线程
    • 空闲回收时间 :超过最小线程的线程,空闲 1 秒后自动销毁

1. 线程池工作线程:扩容逻辑

理论原理

线程池不会瞬间创建大量线程,而是非常保守地扩容,防止 CPU 爆炸。

扩容流程:

  1. 任务进来 → 检查是否有空闲线程
  2. 无空闲 → 检查当前线程数 < 最小线程数?
    • 是:立即创建新线程(0 等待)
    • 否:进入 500ms 限流创建
  3. 每 500ms 只允许新增 1 个线程
  4. 直到达到最大线程数,停止扩容

代码演示:触发线程池扩容

cs 复制代码
using System;
using System.Threading;
using System.Threading.Tasks;

class ThreadPoolDemo
{
    static void Main()
    {
        // 一、手动设置线程池,方便观察:最小 3 个,最大 10 个
        ThreadPool.SetMinThreads(3, 3);
        ThreadPool.SetMaxThreads(10, 10);

        Console.WriteLine("=== 开始提交 10 个长时间任务 ===");

        // 二、一次性扔 10 个任务,触发扩容
        for (int i = 0; i < 10; i++)
        {
            int num = i;
            ThreadPool.QueueUserWorkItem(Worker, num);
        }

        // 三、后台监控线程池状态(1 秒打印一次)
        Task.Run(Monitor);

        Console.ReadLine();
    }

    // 长时间工作任务:让线程一直占用,不释放
    static void Worker(object state)
    {
        int id = (int)state;
        Console.WriteLine($"[任务 {id}] 运行中 | 线程ID: {Thread.CurrentThread.ManagedThreadId}");
        
        // 模拟耗时 10 秒,让线程一直忙,迫使线程池不断扩容
        Thread.Sleep(10000);
    }

    // 监控:打印繁忙线程数、空闲线程数
    static async Task Monitor()
    {
        while (true)
        {
            ThreadPool.GetMaxThreads(out int maxWorker, out _);
            ThreadPool.GetAvailableThreads(out int availWorker, out _);
            int busyWorker = maxWorker - availWorker;

            Console.WriteLine($"\n[监控] 繁忙工作线程:{busyWorker} | 可用空闲:{availWorker}");

            await Task.Delay(1000);
        }
    }
}

运行结果分析(扩容现象)

你会看到控制台输出:

  1. 3 个任务立即执行(因为最小线程 = 3)
  2. 第 4 个任务不会马上运行,要等 500ms 才会新建线程运行
  3. 第 5、6... 个任务 每隔 500ms 才会运行一个
  4. 线程数缓慢上涨,直到 10 个(最大线程)

这就是 500ms 扩容限流

2. 线程池工作线程:空闲逻辑

当线程执行完任务,不会立刻销毁 ,而是进入空闲等待状态

  1. 线程完成任务
  2. 去线程池队列尝试获取下一个任务
  3. 没有任务 → 进入空闲等待
  4. 空闲时:
    • 不占用 CPU
    • 处于内核等待状态
    • 随时可以被新任务唤醒
    • 线程依然存活,只是休眠

观察空闲状态

cs 复制代码
static void Main()
{
    ThreadPool.SetMinThreads(2, 2);

    Console.WriteLine("提交 1 个短任务");
    ThreadPool.QueueUserWorkItem((_) =>
    {
        Console.WriteLine("任务执行完成");
        // 执行完后,线程不会退出,进入空闲等待
    });

    Console.ReadLine();
}

现象

任务执行完后,线程不会消失 ,它回到线程池,处于空闲休眠状态。

3. 线程池工作线程:回收逻辑

线程池只回收多余的线程,最小线程数以内的永久保活。

回收规则:

  1. 线程处于空闲等待

  2. 空闲时间 ≥ 1 秒

  3. 当前总线程数 > 最小线程数满足:线程被销毁、回收、释放内存

  4. 当前总线程数 ≤ 最小线程数不回收,永久保活

    cs 复制代码
    最小以内永久留,超出部分闲 1 秒就杀。

    线程回收

    cs 复制代码
    static void Main()
    {
        // 最小 2 个线程
        ThreadPool.SetMinThreads(2, 2);
        ThreadPool.SetMaxThreads(10, 2);
    
        Console.WriteLine("=== 提交 5 个任务,触发扩容 ===");
        for (int i = 0; i < 5; i++)
        {
            ThreadPool.QueueUserWorkItem((_) =>
            {
                Console.WriteLine($"任务运行 | 线程ID:{Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(2000); // 运行 2 秒
                Console.WriteLine("任务结束");
            });
        }
    
        Task.Run(Monitor);
        Console.ReadLine();
    }

    运行结果分析(回收现象)

  5. 运行时,繁忙线程数涨到 5

  6. 2 秒后所有任务结束

  7. 等待 1 秒

  8. 你会看到监控:繁忙线程从 5 → 慢慢降到 2(超过最小线程的 3 个线程被回收销毁)

4. 流程图

任务来了

cs 复制代码
新任务 → 有空闲线程?→ 是:直接执行
                ↓ 否
当前线程数 < 最小线程数?→ 是:立即新建线程
                ↓ 否
进入 500ms 等待 → 创建 1 个线程
重复直到达到最大线程数

任务执行完

cs 复制代码
任务结束 → 线程回到线程池 → 等待新任务
                      ↓ 等待超时(1秒)
当前线程数 > 最小线程数?→ 是:销毁线程(回收)
                      ↓ 否
继续等待(永久保活)

5. 总结

  • 扩容 小于最小线程:立即创建 大于最小线程:每 500ms 创建 1 个

  • 空闲 线程执行完任务,不退出,休眠等待新任务,不占 CPU

  • 回收 大于最小线程的部分:空闲 1 秒 → 销毁 小于等于最小线程:永久不回收

相关推荐
njsgcs1 小时前
c# solidworks createline 拉伸发现有微小两点间隙 导致拉伸变成薄壁特征 改bug画了6个小时 解决结果
c#·bug·solidworks
时光追逐者1 小时前
一款基于 C# 开发的 Windows 10/11 系统增强工具,精简、优化、定制一站完成!
开发语言·windows·c#·.net
绿豆人1 小时前
进入内核-中断开启
开发语言·c#
步步为营DotNet1 小时前
.NET 11 中 C# 14 新特性在云原生微服务安全与性能优化的深度探索
云原生·c#·.net
工程师0072 小时前
C# foreach 为什么不能增删、迭代器底层原理、版本号机制、以及所有能遍历中增删的方案
c#·foreach·迭代器底层
顾温10 小时前
default——C#/C++
java·c++·c#
InCerry11 小时前
.NET性能优化:提升Apache Arrow读写性能
c#·.net周刊
黑咩狗夜.cm15 小时前
(aspose.words .net)内容分别固定在一行左右俩端
c#·word·.net
刚子编程16 小时前
C# Join 实战:左连接写法、字符串拼接与 EF Core 性能调优
开发语言·c#·solr·join