当前位置: 首页 > article >正文

聊一聊 C#线程池 的线程动态注入

提高注入速度的两种方法

1. 降低GateThread的延迟时间

上一篇跟大家聊过 Result 默认情况下GateThread每秒会注入4个,底层逻辑是由 Blocking.MaxDelayMs=250ms 变量控制的,言外之意就是能不能减少这个变量的值呢?当然可以的,这里我们改成 100ms,参考代码如下:


        static void Main(string[] args)
        {
            AppContext.SetData("System.Threading.ThreadPool.Blocking.MaxDelayMs", 100);

            for (int i = 0; i < 10000; i++)
            {
                ThreadPool.QueueUserWorkItem((idx) =>
                {
                    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} -> {idx}: 这是耗时任务");

                    try
                    {
                        var client = new HttpClient();
                        var content = client.GetStringAsync("https://youtube.com").Result;
                        Console.WriteLine(content.Length);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }, i);
            }

            Console.ReadLine();
        }

现在我们还是用上一篇的方法在如下三个方法 HasBlockingAdjustmentDelayElapsed,PerformBlockingAdjustment,CreateWorkerThread 上埋日志断点,埋好之后运行程序观察。

从卦中的输出结果看,注入速度明显快了很多,判断阈值也从 250ms 变成了 100ms,每秒能注入7~8个线程,所以这是一个简单粗暴的提速方法。

2. 提高 MinThreads 的阈值

看过上两篇的朋友应该知道,我用过 喷涌而出 四个字来形容前 12个线程,这里的12是因为我的机器是 12 核,言外之意就是为什么要设置12呢?我能不能给它提升到 120,1200甚至更高的 12000 呢?这样线程的注入速度不是更快吗?有了这个想法赶紧上一段代码,参考如下:


        static void Main(string[] args)
        {
            ThreadPool.SetMinThreads(10000, 10);

            for (int i = 0; i < 10000; i++)
            {
                ThreadPool.QueueUserWorkItem((idx) =>
                {
                    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} -> {idx}: 这是耗时任务");

                    Thread.Sleep(int.MaxValue);
                }, i);
            }

            Console.ReadLine();
        }

从卦中看,直接秒了这个 10000 个任务,但不要忘了你的程序此时有1w个线程,如果是32bit程序大概率因为虚拟地址不足直接崩了,如果是 64bit 可能也会导致非常可观的内存占用。

有些人可能对底层逻辑感兴趣,我特意花了点时间绘了一张图来描述底层的运转逻辑。

之所以能快速的产生新线程,核心判断条件是 numProcessingWork <= counts.NumThreadsGoal ,我们设置的 MinThread=10000 最后给到了 NumThreadsGoal 字段,所以现有线程数不超过 10000 的话,就会不断的调用 CreateWorkThread 产生新的工作线程。

接下来我们再聊一下 SetMinThreads 这里面的坑吧,如果你将刚才的 ThreadPool.SetMinThreads(10000, 10); 改成 ThreadPool.SetMinThreads(10000, 10000);的话,将不会有任何效果,截图如下:

为什么会出现这样的情况呢?这得从源码上找答案,参考代码如下:


        public class PortableThreadPool
        {
            private short _minThreads;
            private short _maxThreads;
            private short _legacy_maxIOCompletionThreads;
            private const short DefaultMaxWorkerThreadCount = MaxPossibleThreadCount;
            private const short MaxPossibleThreadCount = short.MaxValue;

            private PortableThreadPool()
            {
                _minThreads = HasForcedMinThreads ? ForcedMinWorkerThreads : (short)Environment.ProcessorCount;
                _maxThreads = HasForcedMaxThreads ? ForcedMaxWorkerThreads : DefaultMaxWorkerThreadCount;
                _legacy_maxIOCompletionThreads = 1000;
            }
        }

        public bool SetMinThreads(int workerThreads, int ioCompletionThreads)
        {
            if (workerThreads < 0 || ioCompletionThreads < 0)
            {
                return false;
            }
            bool flag = false;
            bool flag2 = false;
            this._threadAdjustmentLock.Acquire();
            if (workerThreads > (int)this._maxThreads)
            {
                return false;
            }
            if (ioCompletionThreads > (int)this._legacy_maxIOCompletionThreads)
            {
                return false;
            }
        }

从卦中代码可以看到 ioCompletionThreads 默认最大值为 1000,如果你设置的值大于 1000 的话,那前面的 workerThreads 等于白设置了。。。这就很无语了。。。 如果参数有误,你完全可以抛出一个异常来告诉我,,,而不是偷偷的掩埋错误信息,导致程序出现了我意想不到的行为。。。

为了凑篇幅,我再说一个有意思的参数 DebugBreakOnWorkerStarvation,它可以用来捕获 线程饥饿 的第一现场,底层逻辑是C#团队在代码里埋了一个钩子,参考如下:


        private static void GateThreadStart()
        {
            bool debuggerBreakOnWorkStarvation = AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", false);

            while (counts.NumProcessingWork < threadPoolInstance._maxThreads && counts.NumProcessingWork >= counts.NumThreadsGoal)
            {
                if (debuggerBreakOnWorkStarvation)
                {
                    Debugger.Break();
                }
            }
        }

这个 Debugger.Break(); 发出的 int 3 信号,我们可以用 VS,DnSpy,WinDbg 这样的调试器去捕获,参考代码如下:


        static void Main(string[] args)
        {
            AppContext.SetSwitch("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", true);

            for (int i = 0; i < 10000; i++)
            {
                ThreadPool.QueueUserWorkItem((idx) =>
                {
                    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} -> {idx}: 这是耗时任务");

                    Thread.Sleep(int.MaxValue);
                }, i);
            }

            Console.ReadLine();
        }

三:总结

我们聊到了两种提升线程注入的方法,尤其是第二种让人意难平,面对上游洪水猛兽般的对线程池进行DDOS攻击,下游的线程不顾一切,倾家荡产的去承接,这是一种明知不可为而为之的悲壮之举

文章转载自:一线码农

原文链接:https://www.cnblogs.com/huangxincheng/p/18630175

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构


http://www.kler.cn/a/454325.html

相关文章:

  • WebRTC 环境搭建
  • myql explain sql分析详解
  • 【Ubuntu 20.4安装截图软件 flameshot 】
  • STM32学习(一)
  • 【微信小程序】3|首页搜索框 | 我的咖啡店-综合实训
  • Educational Codeforces Round 173 (Rated for Div. 2)
  • Flutter persistentFooterButtons控件详解
  • 【EI会议征稿】人工智能与遥感应用国际会议 (AIRSA 2025)
  • uniapp popup弹窗组件的自定义使用方法
  • Large Language Model based Multi-Agents: A Survey of Progress and Challenges
  • (补)算法刷题Day24: BM61 矩阵最长递增路径
  • 本地部署 LLaMA-Factory
  • 解决:excel鼠标滚动幅度太大如何调节?
  • (NIPS-2024)PISSA:大型语言模型的主奇异值和奇异向量适配
  • 社区二手物品交易小程序ssm+论文源码调试讲解
  • 如何通过HTTP API插入或更新Doc
  • Android Framework 目录下的 AV/Camera 定制常见问题及解决方法
  • Coding(Jenkinsfile)+ Docker 自动化部署 Springboot —— 图文细节和一些注意事项说明
  • 【NIFI】实现ORACLE->ORACLE数据同步
  • Springboot 整合 Duird
  • 【计算机网络安全】加密解密及其在ssh上的应用
  • 面试场景题系列:设计支付系统
  • UnoCSS 的作用与特点
  • idea配置gitee仓库
  • 讯飞语音听写WebApi(流式)【React Native版】
  • 报警推送消息升级的名厨亮灶开源了。