如何設計一個實用的線程池?
原因排查
經過一個多小時的代碼排查終於查明了線上程序線程數過多的原因:這是一個接收MQ消息的一個服務,程序大體思路是這樣的,監聽的線程每次收到一條消息,就啟動一個線程去執行,每次啟動的線程都是新的。
說到這裡,咱們就談一談這個程序有哪些弊端:
每次收到一條消息都創建一個新的線程,要知道線程的資源對於系統來說是很昂貴的,消息處理完成還要銷毀這個線程;
這個程序用到的線程數量是沒有限制的。當線程到達一定數量,程序反而因線程在cpu切換開銷的原因處理效率降低。無論的你的伺服器cpu是多少核心,這個現象都有發生的可能。
解決問題
線程多的問題該怎麼解決呢,增加cpu核心數?治標不治本。對於開發者而言,最為常用也最為有效的是線程池化,也就是說線程池。
線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程後自動啟動這些任務。這避免了在處理短時間任務時創建與銷毀線程的代價。線程池不僅能夠保證內核的充分利用,還能防止過分調度。可用線程數量應該取決於可用的並發處理器、處理器內核、內存、網路sockets等的數量。例如,線程數一般取cpu數量 2比較合適,線程數過多會導致額外的線程切換開銷。
線程池其中一項很重要的技術點就是任務的隊列,隊列雖然屬於一種基礎的數據結構,但是發揮了舉足輕重的作用。
隊列
隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。
隊列是一種採用的FIFO(first in first out)方式的線性表,也就是經常說的先進先出策略。
實現
1、數組
隊列可以用數組Q[1…m]來存儲,數組的上界m即是隊列所容許的最大容量。在隊列的運算中需設兩個指針:head,隊頭指針,指向實際隊頭元素 1的位置;tail,隊尾指針,指向實際隊尾元素位置。一般情況下,兩個指針的初值設為0,這時隊列為空,沒有元素。以下為一個簡單的實例(生產環境需要優化):
publicclassQueueArray
{
//隊列元素的數組容器
T[] container = null;
intIndexHeader, IndexTail;
publicQueueArray(intsize)
{
container =newT[size];
IndexHeader =;
IndexTail =;
}
publicvoidEnqueue(T item)
{
//入隊的元素放在頭指針的指向位置,然後頭指針前移
container[IndexHeader] = item;
IndexHeader ;
}
publicTDequeue()
{
//出隊:把尾元素指針指向的元素取出並清空(不清空也可以)對應的位置,尾指針前移
T item = container[IndexTail];
container[IndexTail] =default(T);
IndexTail ;
returnitem;
}
}
2、鏈表
隊列採用的FIFO(first in first out),新元素總是被插入到鏈表的尾部,而讀取的時候總是從鏈表的頭部開始讀取。每次讀取一個元素,釋放一個元素。所謂的動態創建,動態釋放。因而也不存在溢出等問題。由於鏈表由元素連接而成,遍歷也方便。以下是一個實例僅供參考:
publicclassQueueLinkList
{
LinkedList contianer = null;
publicQueueLinkList()
{
contianer =newLinkedList();
}
publicvoidEnqueue(T item)
{
//入隊的元素其實就是加入到隊尾
contianer.AddLast(item);
}
publicTDequeue()
{
//出隊:取鏈表第一個元素,然後把這個元素刪除
T item = contianer.First.Value;
contianer.RemoveFirst();
returnitem;
}
}
※Google AI 騙過了 Google,工程師竟無計可施?
※從 SAS到NVMe,換個底盤就完兒事了?
TAG:CSDN |