一:背景

1. 讲故事

前面二篇我们聊到了 Thread.SleepTask.Result 场景下的线程注入逻辑,在线程饥饿的情况下注入速度都不是很理想,那怎么办呢?有没有更快的注入速度,这篇作为 动态注入 的终结篇,我个人总结如下两种方法,当然可能有更多的路子,知道的朋友可以在下面留言。

二:提高注入速度的两种方法

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攻击,下游的线程不顾一切,倾家荡产的去承接,这是一种明知不可为而为之的悲壮之举。

聊一聊 C#线程池 的线程动态注入 (下)的更多相关文章

  1. Java多线程系列 JUC线程池03 线程池原理解析(二)

    转载  http://www.cnblogs.com/skywang12345/p/3509954.html  http://www.cnblogs.com/skywang12345/p/351294 ...

  2. Java多线程系列 JUC线程池02 线程池原理解析(一)

    转载  http://www.cnblogs.com/skywang12345/p/3509960.html ; http://www.cnblogs.com/skywang12345/p/35099 ...

  3. java并发编程(十七)----(线程池)java线程池架构和原理

    前面我们简单介绍了线程池的使用,但是对于其如何运行我们还不清楚,Executors为我们提供了简单的线程工厂类,但是我们知道ThreadPoolExecutor是线程池的具体实现类.我们先从他开始分析 ...

  4. 戏(细)说Executor框架线程池任务执行全过程(下)

    上一篇文章中通过引入的一个例子介绍了在Executor框架下,提交一个任务的过程,这个过程就像我们老大的老大要找个老大来执行一个任务那样简单.并通过剖析ExecutorService的一种经典实现Th ...

  5. 由浅入深理解Java线程池及线程池的如何使用

    前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory ...

  6. 基于线程池的线程管理(BlockingQueue生产者消费者方式)实例

    1.线程池管理类: public class ThreadPoolManager { private static ThreadPoolManager instance = new ThreadPoo ...

  7. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  8. Java多线程、线程池和线程安全整理

    多线程 1.1      多线程介绍 进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 1.2      Thread类 通 ...

  9. ReentrantLock+线程池+同步+线程锁

    1.并发编程三要素? 1)原子性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行. 2)可见性 可见性指多个线程操作一个共享变量时,其中一个线程对变量 ...

  10. .NET线程池最大线程数的限制-记一次IIS并发瓶颈

    .NET ThreadPool 最大线程数的限制 IIS并发瓶颈,有几个地方,IIS线程池的最大队列数,工作进程数,最大并发数.这些这里就不展开.主要是最近因为过度使用Task 导致的线程数占用过多, ...

随机推荐

  1. docker-compose -- 创建 redis && mysql

    version: '3' services: nest-admin-web: image: buqiyuan/vue3-antdv-admin:stable container_name: nest- ...

  2. electon的入口文件 main 指定

    任何 Electron 应用程序的入口都是 main 文件. 这个文件控制了主进程,它运行在一个完整的Node.js环境中,负责控制您应用的生命周期,显示原生界面,执行特殊操作并管理渲染器进程(稍后详 ...

  3. CSP模拟10--总结

    今天是我第一次给模拟赛写正规总结--因为今天的题真的受不了了 四道数学题,一点都不拖泥带水的纯血数学题! T1.黑暗型高松灯 shit 本来是一道放在T4防AK的题,结果学长为了 恶心 锻炼一下我们, ...

  4. 云原生爱好者周刊:K8s Security SIG 发布 Kubernetes 策略管理白皮书

    云原生一周动态要闻: Istio 1.13 发布 CNCF 宣布 2021 年云原生调查结果 运行时安全项目 Falco 添加可扩展插件框架 Grafana 8.3.6 发布 开源项目推荐 文章推荐 ...

  5. 这个Linux你敢用吗?

    文中列出的命令绝对不可以运行,即使你觉得很好奇也不行,除非你是在虚拟机上运行(出现问题你可以还原),因为它们会实实在在的破坏你的系统.所以不在root等高级管理权限下执行命令是很好的习惯. 早晚有一天 ...

  6. RocketMQ 全链路灰度探索与实践

    本文作者:肖京,Spring Cloud Alibaba PMC,阿里云智能技术专家. 01 全链路灰度背景介绍 发布新版本时,为了有效.谨慎地验证新版本代码逻辑的正确性,通常会采用灰度发布,从而达到 ...

  7. java程序设置开机自启

    Linux系统jar包开机自启 第一步:创建service文件 sudo nano etc/systemd/system/myapp.service 第二步:将下面代码复制到刚才创建的文件里面,保存 ...

  8. DRF-Version组件源码分析

    1. 版本管理组件源码分析 注意点: 不同的versioning_class区别:实例化后得到的对象versioning_scheme里面的方法不同(函数同名,但是处理逻辑不同) def determ ...

  9. 使用MySQL Workbench进行数据库备份

    1.打开MySQL Workbench 2.进行数据库连接配置 如果之前连过,会有历史记录,直接点击需要备份的连接即可 3.进入主界面后,选择左侧的Administration选项卡,然后点击Data ...

  10. CUDA编程学习 (3)——内存和数据定位

    1. CUDA Memories 1.1 GPU 性能如何 所有 thread 都会访问 global memory,以获取输入的矩阵元素 在执行一次浮点加法时,需要进行一次内存访问,每次访问传输 4 ...