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

news/2025/1/2 0:58:24/

提高注入速度的两种方法

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.ppmy.cn/news/1559477.html

相关文章

通过百度api处理交通数据

通过百度api处理交通数据 1、读取excel获取道路数据 //道路名称Data EqualsAndHashCode public class RoadName {ExcelProperty("Name")private String name; }/*** 获取excel中的道路名称*/private static List<String> getRoadName() {// 定义文件路径&…

计算机网络:IP地址相关知识总结

目录 一、IP地址的表现形式 1.1 十进制表示形式 1.2 二进制表示形式 1.3 转换示例介绍 二、IP地址的组成 2.1 网络ID 2.2 主机ID 2.3 示例 三、IP地址的分类 3.1 A类地址 3.2 B类地址 3.3 C类地址 3.4 D类地址 3.5 E类地址 四、常见的特殊IP地址 五、IP地址二进…

Leetcode - 146双周赛

目录 一&#xff0c;3392. 统计符合条件长度为 3 的子数组数目 二&#xff0c;3393. 统计异或值为给定值的路径数目 三&#xff0c;3394. 判断网格图能否被切割成块 四&#xff0c;3395. 唯一中间众数子序列 I 一&#xff0c;3392. 统计符合条件长度为 3 的子数组数目 本题…

pytorch torch.nn.LayerNorm类介绍

torch.nn.LayerNorm 是 PyTorch 中的一种标准化层,用于对输入的特征进行归一化。它在自然语言处理和序列建模中非常常见,可以帮助模型更快地收敛,并提高泛化能力。 关于类、层和模块 torch.nn.LayerNorm 是 一个类,它是 PyTorch 中标准化操作的实现,继承自 torch.nn.Modu…

上位机开发 的算法与数据结构

Python基础 Python是一种广泛使用的高级编程语言&#xff0c;以其简单易读的语法和强大的功能赢得了众多开发者的青睐。自1991年首次发布以来&#xff0c;Python已经经历了多个版本的更新&#xff0c;当前最新的稳定版本是Python 3.x。Python不仅适用于web开发、数据分析、人工…

【微服务】整合Nacos注册中心和动态配置

文章目录 1.Docker安装Nacos1.拉取镜像2.启动nacos3.开启8848和9848端口1.88482.9848 4.访问Nacos 2.项目集成Nacos的服务发现1.引入依赖1.sun-dependencies 指定版本2.sun-cloud-nacos引入服务发现依赖和bootstrap依赖3.注意&#xff1a;修改完sun-dependencies的依赖后clean-…

BUUCTF Pwn ciscn_2019_es_2 WP

1.下载 checksec 用IDA32打开 定位main函数 发现了个假的后门函数&#xff1a; 看看vul函数&#xff1a; 使用read读取 想到栈溢出 但是只有48个 只能覆盖EBP和返回地址 长度不够构造 所以使用栈迁移&#xff1a; 栈迁移需要用到leave ret 使用ROPgadget找地址&#xff1a; …

QWidget应用封装为qt插件,供其他qt应用调用

在之前的文章中,有介绍通过QProcess的方式启动QWidget应用,然后将其窗口嵌入到其他的qt应用中,作为子窗口使用.这篇文章主要介绍qt插件的方式将QWidget应用的窗口封装为插件,然后作为其他Qt应用中的子窗口使用. 插件优点: 与主程序为同一个进程,免去了进程间繁琐的通信方式,…