C# 线程池ThreadPool用法简介

那天,我看到同事小李在公司电脑前忙碌着,喝着咖啡,敲着键盘。
我好奇地走过去,看到他正在写一个处理大量数据的C程序。
突然他停了下来,皱着眉头说:“这个程序运行太慢了,我得想办法。
我好奇地问:“怎么了?”你遇到了什么问题?小李回答:“当我处理大量数据时,我感觉程序运行得很慢。
这可能是线程管理问题。
思考了一下之前学过的线程池知识。

我告诉他:“你可以尝试使用线程池来优化你的程序。
线程池可以让你更高效地管理线程,避免频繁创建和销毁线程的开销。
”小李听了我的建议,眼睛一亮,他问我:“线程池具体怎么用?”我笑着回答:“很简单,先设置线程池的最大线程数,然后将任务添加到线程池中。

小李点点头,开始按照我的建议修改代码。
不久后,他兴奋地宣称:“真的有效!”运行速度明显提升!我看了看,发现线程池非常有用。
然而,我意识到,如果任务非常复杂,线程池的好处可能不会那么明显。
这时,小李问我:“我们什么时候用Task最好?”我微笑着想,这是个好问题,但这是一个新话题。

上海某小公司面试题:Java线程池来聊聊

昨天我正在帮助一位同事调试一个在线 Java 应用程序,结果它崩溃了。
经过检查,发现线程池满了,作业队列爆了。
这件事突然让我想起了我写第一句话池时的尴尬事件。

我记得刚进入这个行业的时候,负责一个电商项目的后端处理优化。
当时使用Executors.newFixedThreadPool(1 0)来创建线程池。
结果在双十一促销期间被彻底封杀。
后台处理订单的任务都在排队,但线程池只有1 0个固定线程,太忙了。
后来查了源码之后;我们发现原来的LinkedBlockingQueue出现了越界,不断向其中添加任务导致内存爆炸。

回想起来,Executors工厂方法必须谨慎使用。
例如,SingleThreadExecutor;你以为它只是一个线程,但实际上它背后还有一个BlockingQueue。
上次重构代码的时间。
一位前同事发现他用了它,并恢复了数万个工作岗位。
经过两天的排查,终于线路满了我找到了,但它不符合拒绝政策,所以我丢失了所有任务。

最烦人的是RejectedExecutionHandler。
在测试期间,我故意填满队列,但系统放弃了该作业。
后来在写单元测试的时候,我特意添加了异常处理。
我记得那是2 02 1 年3 月的一个下午,我在实验室花了一些时间,最终使用ThreadPoolExecutor直接重写DiscardPolicy。

在当前的项目中,我们都是手动实现一个新的ThreadPoolExecutor。
例如,处理高并发请求时。
设置核心线程数为CPU核心数;多开几个max线程,队列使用ArrayBlockingQueue;并根据内存估计大小。
上次调整参数是在去年1 2 月。
将队列从 1 ,000 更改为 2 ,000 时。
在线响应时间瞬间下降 3 0%。
但现在我在想,使用 PriorityBlockingQueue 会更好吗?是的,我必须再考虑一下这个问题。

Callable原理,线程池执行Callable任务

哦,说一下这个Callable和线程池以及我当年遇到的坑。

2 01 8 年,我刚刚开始从事电子商务项目。
当时,需求非常大,我们想举办一场闪购活动。
起初我直接使用Runnable,但是运行任务后我发现我什至不知道将结果发送到哪里。
客户不希望这样。
您需要实时查看您的订单号。
最后,我变得太大了,强迫自己学习Callable。

Callable的原理其实并不难。
如您所知,这不会像 Runnable 那样返回值,但它可以抛出异常并返回指定类型的结果。
比如我的秒杀任务是使用Callable实现的,call方法直接返回订单号。
接下来,实现一个 Runnable 并在保存 Callable 结果的 FutureTask 中使用它。
调用FutureTask的get方法。
在获得结果之前任务不会结束。

对于线程池,我使用了ThreadPoolExecutor。
在定义核心线程数、最大线程数和队列大小时,我最初是随机设置的,但后来发现,如果任务太多,线程就会爆炸,服务器就会崩溃。
之后,我调整了一些参数,例如将核心线程数设置为1 0,并使用LinkedBlockingQueue作为队列,事情逐渐稳定下来。

当你使用submit提交任务时,它会返回一个Future对象。
我们遍历并提交了1 00个闪购任务,并一一调用了Future的get方法。
一开始我并没有在意,但是当调用get方法的那一刻,get就被阻塞了,程序就停止了。
然后我变得更聪明,学会了要么先发送其他任务而不立即得到结果,要么使用 future.get(long timeout, TimeUnit Unit) 设置超时。

我堵住了线水坑,踩到了一个大洞。
有一次,项目完成后,直接调用shutdown,却发现还有未完成的任务,线程池一直停止。
然后我切换到 shutdownNow 强制中止所有任务并释放资源。

重要的是Callable + FutureTask + 线程池。
如果你正确地使用这三个,并发任务管理将会很有趣。
当时我们正在进行闪购。
如果我早点明白这一点,我就能节省很多夜晚。
但这个使用频繁,需要一一调整参数和处理异常,别想着一口吃掉一个胖子。

.NET多线程(三)线程池

你好,你的总结很全面,大部分都说到了重点。
不过,有些地方我会帮你过一遍,避免误会。

我们只讨论线程池。
你提到的几点几乎都是正确的。
创建和销毁线程的开销非常大,因此将它们放回池中以供重用是核心思想。
但这里必须指定,线程池线程在使用时可以被标记为“后台线程”。
这不像后台线程那么简单,而且很容易迷惑人。
此外,线程池的大小也有上限。
你写得对。

关于ASP.NET的线程池,你说的基本正确,但是也将线程具体分为工作线程和I/O线程,不详细。
工作线程处理CPU密集型任务,比如算法,IO线程专门处理网络读写,不消耗CPU。
两个线程池是分开的,这一点非常重要,但一般开发者不太可能手动干预。

我们来谈谈内存问题。
你的分析是正确的。
太多的线程肯定会占用内存,系统可能会挂掉。
线程池的好处是,当太多请求超过线程池的上限时,新的请求就会排队等待。
虽然速度较慢,但​​总比系统崩溃好。

至于如何使用线程池,你列出的例子都是准确的:
QueueUserWorkItem 只是将任务扔到工作线程池中,这很简单。

委托异步调用(BeginInvoke)使用WorkerThread。

System.Threading.Timer默认也使用WorkerThread。

BackgroundWorker也使用WorkerThread,但是它有自己的UI更新机制,比手写委托更方便。

最后一个CancellationTokenSource,你是想取消任务吧?这与线程池一起工作,但它不是线程池本身的功能,而是任务取消机制。

总的来说,你的理解力还是不错的。
“后台线程”和“从属线程”这两个术语很容易混淆。
说清楚。
还有一点,ASP.NET的线程池分为工作线程和IO线程。
虽然通常不需要手动管理它,但知道它的存在还是很好的。

如果您有任何疑问,请随时问我。
别自己想办法。