.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 秒 → 销毁 小于等于最小线程:永久不回收

相关推荐
huaqianzkh1 天前
WinForm C#:循环修改控件UI闪烁卡顿?SuspendLayout / ResumeLayout 与 BeginUpdate 优化
开发语言·ui·c#
用户3721574261352 天前
C# 实现 Excel 单元格保护:锁定公式并允许编辑指定区域
c#·ai编程·编程语言
公子小六2 天前
基于.NET的Windows窗体编程之WinForms数据持久化(1)
windows·microsoft·c#·.net·winforms
三雷科技2 天前
第6章第4节:自定义分词器
开发语言·c#
zhanghaha13142 天前
C# 系列教程_12 字符串 string 保姆级系统教程(面向新手,大量示例 + 原理讲解)
开发语言·数据库·c#
Coder_Shenshen3 天前
罗克韦尔ACD工程归档离线变量树解析
算法·c#
寂寞的船3 天前
【Unity知识速递】第10期 Unity GC 零分配实战:从 UnityEngine.Pool 到手动 GC 控制的全链路优化
jvm·unity·c#·游戏引擎·lucene
1-1=03 天前
C# 数据结构和特殊类型
数据结构·c#·.net·.netcore
波力海苔夹心脆6755 天前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典
AI导出鸭PC端5 天前
Perplexity批量导出没有PDF模式?AI导出鸭插件补上最后一块拼图
人工智能·pdf·c#·ai导出鸭