線程池的基本概念
什么是線程池?
- ** .NET Framework的ThreadPool類提供一個(gè)線程池**
- “線程池”是可以用來在后臺(tái)執(zhí)行多個(gè)任務(wù)的線程集合
- 線程池通常用于服務(wù)器應(yīng)用程序。 每個(gè)傳入請(qǐng)求都將分配給線程池中的一個(gè)線程,因此可以異步處理請(qǐng)求,而不會(huì)占用主線程,也不會(huì)延遲后續(xù)請(qǐng)求的處理
- 一旦池中的某個(gè)線程完成任務(wù),它將返回到等待線程隊(duì)列中,等待被再次使用。 這種重用使應(yīng)用程序可以避免為每個(gè)任務(wù)創(chuàng)建新線程的開銷
- 線程池通常具有最大線程數(shù)限制。 如果所有線程都繁忙,則額外的任務(wù)將放入隊(duì)列中,直到有線程可用時(shí)才能夠得到處理
為什么要用線程池?
- 線程是非常消耗資源的,如果我們每次需要子線程來執(zhí)行任務(wù),就去創(chuàng)建一個(gè)新的線程,那當(dāng)我們執(zhí)行1千次1萬次甚至是100萬次的時(shí)候,那么電腦的資源消耗就非常嚴(yán)重,甚至承受不了
- 線程池可以避免大量的創(chuàng)建和銷毀的開支,具有更好的性能和穩(wěn)定性
- 開發(fā)人員把線程交給系統(tǒng)管理,可以集中精力處理其他任務(wù)
前臺(tái)線程&后臺(tái)線程
- 前臺(tái)線程: 當(dāng)程序運(yùn)行起來后,主線程已經(jīng)運(yùn)行結(jié)束了,但是程序卻沒有停止,子線程依然在運(yùn)行
- 后臺(tái)線程: 只要主線程和所有的前臺(tái)線程執(zhí)行結(jié)束,就算后臺(tái)線程的任務(wù)還沒完成,也會(huì)強(qiáng)行打斷直接退出
- 特點(diǎn): 后臺(tái)適用于不太重要的任務(wù),即被中斷了也沒事的。 前臺(tái)線程適用于比較重要的任務(wù),必須等任務(wù)執(zhí)行完程序才能關(guān)閉
- ThreadPool 線程池中的線程都是 后臺(tái)線程
線程池的使用
設(shè)置線程池大小
- ThreadPool.SetMaxThreads (int workerThreads,int completionPortThreads)
- ThreadPool.SetMinThreads (int workerThreads,int completionPortThreads)
- 參數(shù)解析:
- completionPortThreads:線程池中異步 I/O 線程的數(shù)目 (I/O 完成線程)
- workerThreads:線程池中輔助線程的數(shù)目**(工作線程)**
- 對(duì)于以上兩個(gè)參數(shù)的解釋,摘自網(wǎng)絡(luò)其它博客:
-
主要用來完成 輸入和輸出的工作 ,在這種情況下, 計(jì)算機(jī)需要I/O設(shè)備完成輸入和輸出的任務(wù)。在處理過程中, CPU是不需要參與處理過程的 , 此時(shí)正在運(yùn)行的線程將處于等待狀態(tài) , 只有等任務(wù)完成后才會(huì)有事可做 , 這樣就造成線程資源浪費(fèi)的問題。為了解決這樣的問題,可以通過線程池來解決這樣的問題,讓線程池來管理線程
-
.NET中的一些API方法,通過APM(異步編程模式),內(nèi)部實(shí)現(xiàn)了ThreadPool.BindHandle方法。BeginXXX方法將用戶的回調(diào)委托送到某個(gè)設(shè)備驅(qū)動(dòng)程序,然后返回線程池。
當(dāng)做完成后,OS會(huì)通過IOCP提醒CLR它工作已經(jīng)完成,當(dāng)接收到通知后,I/O線程會(huì)醒來并且運(yùn)行用戶的回調(diào)
-
所以工作線程由開發(fā)人員調(diào)用, I/O線程由CLR調(diào)用 。所以通常情況下,開發(fā)者并不會(huì)直接用到它。因此可以認(rèn)為, 工作者線程和I/O線程沒有區(qū)別,它們都是普通的線程 , 但是CLR線程池中區(qū)分它們的目的是為了避免線程都去處理I/O回調(diào)而被耗盡,從而引發(fā)死鎖 。(設(shè)想,所有的工作者線程每一個(gè)都去等待I/O異步完成。)
-
用來完成一些 計(jì)算的任務(wù) ,在任務(wù)執(zhí)行的過程中,需要 CPU不間斷地處理 ,所以,在工作者線程的執(zhí)行過程中,CPU和線程的資源是充分利用的
-
.NET中的術(shù)語工作者線程指的是任何線程而不是僅僅主線程
-
工作者線程
-
I/O線程
-
啟動(dòng)線程任務(wù)使用: QueueUserWorkItem()方法
static bool QueueUserWorkItem(WaitCallback callBack)
:參數(shù)為一個(gè)帶一個(gè)object
類型參數(shù)的委托,最后返回bool
值成功則返回true
class ThreadPoolTest
{
static void Main()
{
Console.WriteLine("啟動(dòng)多線程...");
for(int i = 0; i <10; i++)
{
ThreadPool.QueueUserWorkItem( p => printStr("當(dāng)前線程") );
}
Console.WriteLine("結(jié)束多線程...");
Console.ReadKey();
}
private static void printStr(string str)
{
Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
}
}
輸出結(jié)果, 可以看到有很多線程ID是重復(fù)的,這就是線程池的強(qiáng)大之處了
啟動(dòng)多線程...
結(jié)束多線程...
當(dāng)前線程是:8
當(dāng)前線程是:6
當(dāng)前線程是:10
當(dāng)前線程是:9
當(dāng)前線程是:7
當(dāng)前線程是:11
當(dāng)前線程是:12
當(dāng)前線程是:9
當(dāng)前線程是:6
當(dāng)前線程是:10
查看最大/最小線程數(shù) && 設(shè)置最大/最小線程數(shù)
class ThreadPoolTest
{
static void Main()
{
// 獲取默認(rèn)的線程池中的最大,最小線程數(shù)
ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads);
Console.WriteLine("最大線程數(shù),工作線程:{0}, IO線程數(shù):{1}", maxWorkerThreads, maxCompletionPortThreads);
ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads);
Console.WriteLine("最小線程數(shù),工作線程:{0}, IO線程數(shù):{1}", minWorkerThreads, minCompletionPortThreads);
// 重新設(shè)置最大、最小線程數(shù)
ThreadPool.SetMaxThreads(10, 10);
ThreadPool.SetMinThreads(3, 3);
// 獲取默認(rèn)的線程池中的最大,最小線程數(shù)
ThreadPool.GetMaxThreads(out int newMaxWorkThread, out int newMaxIOThread);
Console.WriteLine("重新設(shè)置后的最大線程數(shù),工作線程:{0}, IO線程數(shù):{1}", newMaxWorkThread, newMaxIOThread);
ThreadPool.GetMinThreads(out int newMinWorkThread, out int newMinIOThread);
Console.WriteLine("重新設(shè)置后的最小線程數(shù),工作線程:{0}, IO線程數(shù):{1}", newMinWorkThread, newMinIOThread);
Console.WriteLine("啟動(dòng)多線程...");
for(int i = 0; i <10; i++)
{
ThreadPool.QueueUserWorkItem( p => printStr("當(dāng)前線程") );
}
Console.WriteLine("結(jié)束多線程...");
Console.ReadKey();
}
private static void printStr(string str)
{
Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
}
}
輸出結(jié)果
最大線程數(shù),工作線程:32767, IO線程數(shù):1000
最小線程數(shù),工作線程:8, IO線程數(shù):8
重新設(shè)置后的最大線程數(shù),工作線程:10, IO線程數(shù):10
重新設(shè)置后的最小線程數(shù),工作線程:3, IO線程數(shù):3
啟動(dòng)多線程...
結(jié)束多線程...
當(dāng)前線程是:6
當(dāng)前線程是:9
當(dāng)前線程是:7
當(dāng)前線程是:8
當(dāng)前線程是:11
當(dāng)前線程是:10
當(dāng)前線程是:12
當(dāng)前線程是:11
當(dāng)前線程是:7
當(dāng)前線程是:9
ThreadPool.SetMaxThreads的默認(rèn)值
- 它取決于.NET框架版本,在2.0,3.0和4.0中進(jìn)行了更改。 在2.0中它是核心數(shù)量的50倍。 在3.0(又名2.0 SP1)中,它是內(nèi)核數(shù)量的250倍,4.0根據(jù)位數(shù)和操作系統(tǒng)資源使其動(dòng)態(tài)化。 默認(rèn) Max I / O完成線程是1000
- 一般來說,它是非常的高,一個(gè)程序永遠(yuǎn)不會(huì)接近
使用以上方法設(shè)置線程數(shù)據(jù)大小時(shí)需注意
- 不能將最大工作線程數(shù)或 I/O 完成線程數(shù)設(shè)置為小于計(jì)算機(jī)上的處理器數(shù)的數(shù)字
- 不能將最大工作線程數(shù)或 I/O 完成線程數(shù)設(shè)置為小于相應(yīng)最小工作線程數(shù)或 I/O 完成線程數(shù)的數(shù)字
- 默認(rèn)情況下,最小線程數(shù)設(shè)置為系統(tǒng)上的處理器數(shù)。 可以使用該方法 SetMinThreads 增加最小線程數(shù)。 但是,不必要地增加這些值可能導(dǎo)致性能問題。 如果在同一時(shí)間開始太多的任務(wù),則所有任務(wù)均可能會(huì)很慢。 在大多數(shù)情況下,線程池將使用自己的算法更好地分配線程。 將最小處理器減少到小于處理器數(shù)也會(huì)損害性能
查看系統(tǒng)cpu的處理數(shù)
編輯
**線程等待 **
由于線程池中的線程都是后臺(tái)線程,當(dāng)主線程及所有前臺(tái)線程執(zhí)行完時(shí),后臺(tái)線程就會(huì)被中斷,但如果我們希望 主線程等待 線程池執(zhí)行完成任務(wù)后再關(guān)閉程序 怎么辦呢?
需要使用到ManualResetEvent類
編輯
**ManualResetEvent需要一個(gè)bool類型的參數(shù)來表示暫停和停止
**
class ThreadPoolTest2
{
// 參數(shù)設(shè)置為false
static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
static void Main()
{
Console.WriteLine("啟動(dòng)多線程...");
ThreadPool.QueueUserWorkItem(p => printStr("當(dāng)前線程"));
// 等待 線程池執(zhí)行任務(wù)完成
manualResetEvent.WaitOne();
Console.WriteLine("結(jié)束多線程...");
}
private static void printStr(string str)
{
Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
// 設(shè)置為true
manualResetEvent.Set();
}
}
輸出結(jié)果
啟動(dòng)多線程...
當(dāng)前線程是:6
結(jié)束多線程...
ManualResetEvent類的參數(shù)值執(zhí)行順序如下:
- false-- WaitOne等待
- true--WaitOne通過
注:一般情況下,不要阻塞線程池中的線程,因?yàn)?/strong>寫代碼無意間造成的線程等待沒有釋放,一旦線程池線程耗盡就會(huì)形成死鎖
造成死鎖的案例: 設(shè)置了最大線程數(shù)是9,循環(huán)創(chuàng)建15個(gè)線程,意味著 后6個(gè)線程必然需要利用線程池中前面用過的線程, 但是由于前面創(chuàng)建的線程都直接讓阻塞了,沒有釋放,這時(shí)循環(huán)到第10個(gè)線程時(shí)由于沒有空閑線程,線程池沒法執(zhí)行就直接跳過了,主線程會(huì)繼續(xù)執(zhí)行后面的循環(huán),這樣也永遠(yuǎn)不會(huì) 執(zhí)行 manualResetEvent.Set() 方法,最后就成死鎖了
private static void Test1()
{
// 設(shè)置最大線程
ThreadPool.SetMaxThreads(9, 9);
// 設(shè)置最小線程
ThreadPool.SetMinThreads(3, 3);
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
for (int i = 0; i < 15; i++)
{
int k = i;
ThreadPool.QueueUserWorkItem(p =>
{
Console.WriteLine(k);
if (k < 10)
{
manualResetEvent.WaitOne();
}
else
{
// 設(shè)為true
manualResetEvent.Set();
}
});
}
if (manualResetEvent.WaitOne())
{
Console.WriteLine("沒有死鎖。。。。。。。。。");
}
Console.WriteLine("結(jié)束。。。。。。。。。。");
}
輸出結(jié)果
1
0
2
3
4
5
6
7
8
-
服務(wù)器
+關(guān)注
關(guān)注
12文章
9123瀏覽量
85324 -
應(yīng)用程序
+關(guān)注
關(guān)注
37文章
3265瀏覽量
57677 -
線程池
+關(guān)注
關(guān)注
0文章
57瀏覽量
6844
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論