程序员修仙之路--设计一个实用的线程池
菜菜呀,我最近研究技術呢,發現線上一個任務程序線程數有點多呀
CEO,CTO,CFO于一身的CXOx總,你學編程呢?
菜菜作為公司總負責人,我以后還要管理技術部門呢,怎么能不會技術呢
CEO,CTO,CFO于一身的CXO(技術部完了)。。。。。。。
菜菜趕緊看看線上那個線程特別多的程序,給你2個小時優化一下
CEO,CTO,CFO于一身的CXOx總,我想辭職
菜菜菜菜呀,心不要浮躁,學學小馬,心平氣和養養生
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),新元素總是被插入到鏈表的尾部,而讀取的時候總是從鏈表的頭部開始讀取。每次讀取一個元素,釋放一個元素。所謂的動態創建,動態釋放。因而也不存在溢出等問題。由于鏈表由元素連接而成,遍歷也方便。以下是一個實例僅供參考:
????{
????????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讓我做一個計算器●程序猿修仙之路--數據結構之設計高性能訪客記錄系統●程序猿修仙之路--算法之快速排序到底有多快●程序猿修仙之路--數據結構之你是否真的懂數組?
●程序猿修仙之路--算法之希爾排序!
●程序員修仙之路--算法之插入排序!
●程序員修仙之路--算法之選擇排序!
互聯網之路,菜菜與君一同成長
? ? 長按識別二維碼關注
你點的每個贊,我都認真當成了喜歡總結
以上是生活随笔為你收集整理的程序员修仙之路--设计一个实用的线程池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软上线Try .NET,支持在浏览器运
- 下一篇: 程序员修仙之路-数据结构之 CXO让我做