程序员修仙之路--设计一个实用的线程池
菜菜呀,我最近研究技术呢,发现线上一个任务程序线程数有点多呀
CEO,CTO,CFO于一身的CXO
x总,你学编程呢?
菜菜
作为公司总负责人,我以后还要管理技术部门呢,怎么能不会技术呢
CEO,CTO,CFO于一身的CXO
(技术部完了)。。。。。。。
菜菜
赶紧看看线上那个线程特别多的程序,给你2个小时优化一下
CEO,CTO,CFO于一身的CXO
x总,我想辞职
菜菜
菜菜呀,心不要浮躁,学学小马,心平气和养养生
CEO,CTO,CFO于一身的CXO
............................
菜菜
好了,给你半天时间把线程多的问题优化一下,要不然扣你绩效
CEO,CTO,CFO于一身的CXO
(嘞了个擦)。。。。。。
菜菜◆◆原因排查◆◆
经过一个多小时的代码排查终于查明了线上程序线程数过多的原因:这是一个接收mq消息的一个服务,程序大体思路是这样的,监听的线程每次收到一条消息,就启动一个线程去执行,每次启动的线程都是新的。说到这里,咱们就谈一谈这个程序有哪些弊端呢:
1. 每次收到一条消息都创建一个新的线程,要知道线程的资源对于系统来说是很昂贵的,消息处理完成还要销毁这个线程。
2. 这个程序用到的线程数量是没有限制的。当线程到达一定数量,程序反而因线程在cpu切换开销的原因处理效率降低。无论的你的服务器cpu是多少核心,这个现象都有发生的可能。
◆◆解决问题◆◆
线程多的问题该怎么解决呢,增加cpu核心数?治标不治本。对于开发者而言,最为常用也最为有效的是线程池化,也就是说线程池。
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。
线程池其中一项很重要的技术点就是任务的队列,队列虽然属于一种基础的数据结构,但是发挥了举足轻重的作用。
◆◆队列◆◆
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列是一种采用的FIFO(first in first out)方式的线性表,也就是经常说的先进先出策略。
实现
数组
队列可以用数组Q[1…m]来存储,数组的上界m即是队列所容许的最大容量。在队列的运算中需设两个指针:head,队头指针,指向实际队头元素+1的位置;tail,队尾指针,指向实际队尾元素位置。一般情况下,两个指针的初值设为0,这时队列为空,没有元素。以下为一个简单的实例(生产环境需要优化):
public class QueueArray<T> { //队列元素的数组容器 T[] container = null; int IndexHeader, IndexTail; public QueueArray(int size) { container = new T[size]; IndexHeader = 0; IndexTail = 0; } public void Enqueue(T item) { //入队的元素放在头指针的指向位置,然后头指针前移 container[IndexHeader] = item; IndexHeader++; } public T Dequeue() { //出队:把尾元素指针指向的元素取出并清空(不清空也可以)对应的位置,尾指针前移 T item = container[IndexTail]; container[IndexTail] = default(T); IndexTail++; return item; }
}
链表
队列采用的FIFO(first in first out),新元素总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表由元素连接而成,遍历也方便。以下是一个实例仅供参考:
public class QueueLinkList<T> { LinkedList<T> contianer = null; public QueueLinkList() { contianer = new LinkedList<T>(); } public void Enqueue(T item) { //入队的元素其实就是加入到队尾 contianer.AddLast(item); } public T Dequeue() { //出队:取链表第一个元素,然后把这个元素删除 T item = contianer.First.Value; contianer.RemoveFirst(); return item; }
}
队列的扩展阅读
1. 队列通过数组来实现的话有什么问题吗?是的。首先基于数组不可变本质的因素(具体可参考菜菜之前的文章),当一个队列的元素把数组沾满的时候,数组扩容是有性能问题的,数组的扩容过程不只是开辟新空间分配内存那么简单,还要有数组元素的copy过程,更可怕的是会给GC造成极大的压力。如果数组比较小可能影响比较小,但是当一个数组比较大的时候,比如占用500M内存的一个数组,数据copy其实会造成比较大的性能损失。
2. 队列通过数组来实现,随着头指针和尾指针的位置移动,尾指针最终会指向第一个元素的位置,也就是说没有元素可以出队了,其实要解决这个问题有两种方式,其一:在出队或者入队的过程中不断的移动所有元素的位置,避免上边所说的极端情况发生;其二:可以把数组的首尾元素连接起来,使其成为一个环状,也就是经常说的循环队列。
3. 队列在一些特殊场景下其实还有一些变种,比如说循环队列,阻塞队列,并发队列等,有兴趣的同学可以去研究一下,这里不在展开讨论。这里说到阻塞队列就多说一句,其实用阻塞队列可以实现一个最基本的生产者消费者模式。
4. 当队列用链表方式实现的时候,由于链表的首尾操作时间复杂度都是O(1),而且没有空间大小的限制,所以一般的队列用链表实现更简单
5. 当队列中无元素可出队或者没有空间可入队的时候,是阻塞当前的操作还是返回错误信息,取决于在座各位队列的设计者了。
◆◆简单实用的线程池◆◆
Net Core C# 版本
//线程池 public class ThreadPool { bool PoolEnable = false; //线程池是否可用 List<Thread> ThreadContainer = null; //线程的容器 ConcurrentQueue<ActionData> JobContainer = null; //任务的容器 public ThreadPool(int threadNumber) { PoolEnable = true; ThreadContainer = new List<Thread>(threadNumber); JobContainer = new ConcurrentQueue<ActionData>(); for (int i = 0; i < threadNumber; i++) { var t = new Thread(RunJob); ThreadContainer.Add(t); t.Start(); } } //向线程池添加一个任务 public void AddTask(Action<object> job,object obj, Action<Exception> errorCallBack=null) { if (JobContainer != null) { JobContainer.Enqueue(new ActionData { Job = job, Data = obj , ErrorCallBack= errorCallBack }); }
} //终止线程池 public void FinalPool() { PoolEnable = false; JobContainer = null; if (ThreadContainer != null) { foreach (var t in ThreadContainer) { //强制线程退出并不好,会有异常 //t.Abort(); t.Join(); } ThreadContainer = null; }
} private void RunJob() { while (true&& JobContainer!=null&& PoolEnable) { //任务列表取任务 ActionData job=null; JobContainer?.TryDequeue(out job); if (job == null) { //如果没有任务则休眠 Thread.Sleep(10); continue; } try { //执行任务 job.Job.Invoke(job.Data); } catch(Exception error) { //异常回调 job?.ErrorCallBack(error); } } } }
public class ActionData { //执行任务的参数 public object Data { get; set; } //执行的任务 public Action<object> Job { get; set; } //发生异常时候的回调方法 public Action<Exception> ErrorCallBack { get; set; } }
使用方法
ThreadPool pool = new ThreadPool(100); for (int i = 0; i < 5000; i++) { pool.AddTask((obj) => { Console.WriteLine($"{obj}__{System.Threading.Thread.CurrentThread.ManagedThreadId}"); }, i, (e) => { Console.WriteLine(e.Message); }); } pool.FinalPool(); Console.Read();
●程序员修仙之路--数据结构之CXO让我做一个计算器●程序猿修仙之路--数据结构之设计高性能访客记录系统●程序猿修仙之路--算法之快速排序到底有多快●程序猿修仙之路--数据结构之你是否真的懂数组?
●程序猿修仙之路--算法之希尔排序!
●程序员修仙之路--算法之插入排序!
●程序员修仙之路--算法之选择排序!
互联网之路,菜菜与君一同成长
长按识别二维码关注
你点的每个赞,我都认真当成了喜欢
程序员修仙之路--设计一个实用的线程池相关推荐
- 程序员修仙之路--高性能排序多个文件
点击上方蓝色字体,关注我们 菜菜呀,昨天晚上班级空间崩溃了 程序员主力 Y总 what? 菜菜 我看服务器上写了很多个日志文件,我看着太费劲了,能不能按照日期排序整合成一个文件呀? 程序员主力 Y总 ...
- 程序员修仙之路--把用户访问记录优化到极致
点击上方蓝色字体,关注我们 菜菜呀,前几天做的用户空间,用户反映有时候比较慢呀 CEO,CTO,CFO于一身的CXO 是吗? 菜菜 我把你拉进用户反馈群,你解决一下呀 CEO,CTO,CFO于一身的C ...
- 程序员修仙之路-数据结构之 CXO让我做一个计算器
菜菜呀,个税最近改革了,我得重新计算你的工资呀,我需要个计算器,你开发一个吧 CEO,CTO,CFO于一身的CXO X总,咱不会买一个吗? 菜菜 那不得花钱吗,一块钱也是钱呀··这个计算器支持加减乘除 ...
- 实现pv uv统计_程序员修仙之路优雅快速的统计千万级别uv
菜菜,咱们网站现在有多少PV和UV了? Y总,咱们没有统计pv和uv的系统,预估大约有一千万uv吧 写一个统计uv和pv的系统吧 网上有现成的,直接接入一个不行吗? 别人的不太放心,毕竟自己写的,自己 ...
- 程序员修仙之路--优雅快速的统计千万级别uv
菜菜,咱们网站现在有多少PV和UV了? Y总,咱们没有统计pv和uv的系统,预估大约有一千万uv吧 写一个统计uv和pv的系统吧 网上有现成的,直接接入一个不行吗? 别人的不太放心,毕竟自己写的,自己 ...
- 程序员修仙之路-数据结构之设计一个高性能线程池
原因排查 经过一个多小时的代码排查终于查明了线上程序线程数过多的原因:这是一个接收mq消息的一个服务,程序大体思路是这样的,监听的线程每次收到一条消息,就启动一个线程去执行,每次启动的线程都是新的.说 ...
- 如何设计一个实用的线程池?
原因排查 经过一个多小时的代码排查终于查明了线上程序线程数过多的原因:这是一个接收MQ消息的一个服务,程序大体思路是这样的,监听的线程每次收到一条消息,就启动一个线程去执行,每次启动的线程都是新的. ...
- 程序员修神之路--设计一套RPC框架并非易事
菜菜哥,我最近终于把Socket通信调通了 这么底层的东西你现在都会了,恭喜你离涨薪又进一步呀 http协议不也是利用的Socket吗 可以这么说,http协议是基于TCP协议的,底层的数据传输可以说 ...
- 程序员修神之路--分布式缓存的一条明路(附代码)
菜菜呀,由于公司业务不断扩大,线上分布式缓存服务器扛不住了呀 程序员主力 Y总 如果加硬件能解决的问题,那就不需要修改程序 菜菜 我是想加服务器来解决这个问题,但是有个问题呀 程序员主力 Y总 ??? ...
最新文章
- 支付宝支付 - 异步通知与同步通知
- WIN10 软连接释放c盘空间
- lambda中使用filter过滤
- 图片测量尺寸软件_3D扫描之工件测量检测
- Atcoder Grand Contest 026 (AGC026) F - Manju Game 博弈,动态规划
- 支持向量回归预测怎么做_机器学习如何在油气开发预测中发挥作用?
- base64转图片_从一道面试题说起:GET 请求能传图片吗?
- linux虚拟文件系统(六)-文件关闭操作分析
- [转载] Python_range()_逆序
- 成功激活windows server 2008 sp2!
- hello linux
- 查找与清除线程插入式木马(转)
- 《Netty权威指南》笔记 —— 第十二、十三、十四章
- 骨科手术ICD编码主要缺陷与原因分析
- 神经网络控制学习笔记——神经网络背景1
- php解析抖音视频链接,PHP抖音视频无水印解析接口
- Laravel文档 翻译合集
- 时代周报:马云借欺诈门一石三鸟 大规模公关秀
- 我的大学十年 -- 林锐
- php 模块说明,各个模块详情展示页面说明使用