日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C#.Net使用线程池(ThreadPool)与专用线程(Thread)

發布時間:2023/12/10 C# 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#.Net使用线程池(ThreadPool)与专用线程(Thread) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?


線程池(ThreadPool)使用起來很簡單,但它有一些限制:?

1. 線程池中所有線程都是后臺線程,如果進程的所有前臺線程都結束了,所有的后臺線程就會停止。不能把入池的線程改為前臺線 程。?

2. 不能給入池的線程設置優先級或名稱。?

3. 對于COM對象,入池的所有線程都是多線程單元(Multi-threaded apartment,MTA)線程。許多COM對象都需要單線程單元(Single -threaded apartment,STA)線程。?

4.入池的線程只能用于時間較短的任務。如果線程要一直運行(如Word的拼寫檢查器線程),就應使用Thread類創建一個線程。

?




高效線程使用圣典

?

?


  嚴格來講,線程的系統開銷很大。系統必須為線程分配并初始化一個線程內核對象,還必須為每個線程保留1mb的地址空間 (按需提交)用于線程的用戶模式堆棧,分配12kb左右的地址空間用于線程的內核模式堆棧。然后,緊接著線程創建后,windows調 用進程中每個dll都有的一個函數來通知進程中所有的dll操作系統創建了一個新的線程。同樣,銷毀一個線程的開銷也不小:進程 中的每個dll都要接收一個關于線程即將“死亡”的通知,而且內核對象及堆棧還需釋放。


  如果一臺計算機中只有一個cpu,那么在某一時刻只有一個線程可以運行。windows必須跟蹤記錄線程對象,而且是不停地跟 蹤記錄每個線程對象。windows不得不決定cpu下次調度哪個線程來執行。這個額外的代碼不得不每隔20ms左右執行一次。windows使 cpu停止執行一個線程的代碼,而開始執行另一個線程的代碼的現象,我們稱之為上下文切換(context switch)。上下文切換的開 銷相當大,因為操作系統必須執行以下步驟:


  1. 進入內核模式。


  2. 將cpu的寄存器保存到當前正在執行的線程的內核對象中。x86架構的機器上cpu寄存器占了大約700字節的空間;x64架構 的機器上cpu寄存器占了大約1240字節的空間;而在ia64架構的機器上cpu寄存器占了大約2500字節的空間。


  3. 需要一個自旋鎖(spin lock),確定下一次調度哪個線程,然后再釋放該自旋鎖。如果下一次調度的線程屬于另一個進 程,那么此處的開銷會更大,因為操作系統必切換到虛擬地址空間。


  4. 將即將運行的線程的內核對象的值加載到cpu寄存器中。


  5. 退出內核模式。


  所有上述內容都是純粹的開銷,導致windows操作系統和應用程序的執行速度比在單線程系統上的執行速度慢。


  綜合上述所有結果可得出以下結論:應盡可能地限制線程的使用。如果創建的線程越多,給操作系統帶來的開銷就越大,所 有的東西也就運行得越慢。另外,每個線程都需要資源(內核對象占用的內存及兩個堆棧),所以每個線程都會消耗內存。


  線程還有另一個用途:可擴展性。當計算機有多個cpu時,windows能同時調度多個線程:每個cpu運行一個線程。


CLR線程池簡介


  如前所述,創建并銷毀一個線程在時間上的開銷相當大。另外,線程多還會浪費內存資源,而且由于操作系統不得不在可運 行線程間進行調度和上下文切換,從而影響操作系統和應用程序的性能。為改進這種現象,clr中包含管理clr線程池的代碼。我們 可以將線程池看作應用程序自己使用的線程的集合。每個進程都有一個線程池,這個線程池被該進程中的所有應用程序域共享。


  當clr初始化時,線程池中還沒有任何線程。從內部實現上講,線程池維護了一系列操作請求。應用程序希望執行一個異步 操作時,可以調用一些方法在線程池的隊列中加入一個條目。線程池中的代碼將從這個隊列中提取出條目,并將該條目分派到線程 池中的線程。如果線程池中沒有任何線程,就創建一個新的線程。創建一個線程會有相關的性能損失。但是,當線程池中的線程完 成任務時,并不會被銷毀,而是返回到線程池中,在線程池中空閑,等待響應另外的請求。因為線程不對它自身進行銷毀,所以此 處不會帶來性能損失。


  如果應用程序對線程池進行了很多的請求,那么線程池將試圖只用一個線程來響應所有的請求。但是,如果應用程序排隊的 請求超出了線程池的處理能力,線程池中將創建另外的線程。最終,應用程序排隊的請求與線程池中線程的處理能力達到一個平衡 點,我們可以采用較小數量的線程來處理所有的請求,因此線程池中也就不再需要創建更多的線程。


  如果應用程序停止請求線程池,線程池中可能會有許多不做事情的線程。這種情況會浪費內存資源。因此,當線程池中的線 程空閑超過大約2分鐘后,線程將喚醒自己,并終止自己,以釋放內存資源。當線程終止自己時,也會存在一個性能損失。但是,該 性能損失不是很嚴重,因為線程在終止自己時,線程已處于空閑狀態,這意味著我們的應用程序當前沒有執行太多的工作。


  從內部實現上講,線程池將線程池中的線程進行分類,劃分為工作線程(worker thread)和i/o線程(i/o thread)。當應 用程序請求線程池執行一個受計算限制的異步操作(包括初始化受i/o限制的異步操作)時使用工作線程,而i/o線程用于在受i/o限 制的異步操作完成時通知代碼。具體而言,這意味著我們需要使用異步編程模型來進行i/o請求。


限制線程池中的線程數量


  clr的線程池允許開發人員設置工作線程和i/o線程的最大數量。clr保證創建的線程數量不會超過這個設置值。但永遠不要 對線程池中線程的數量設置一個上限,因為饑餓和死鎖現象可能會發生。在clr的2.0版默認中,工作線程的默認最大數量為機器中 每個cpu25個,i/o線程最大數量設為1000個。


  system.threading.threadpool類提供了幾個操作線程池中線程數量的靜態方法:getmaxthreads(查詢線程池對線程數量的 最大限制)、setmax-threads(設置線程數量最大限制)、getminthreads(查詢線程池對線程數量的最小限制)、setminthreads (設置線程數量最小限制)、getavailable-threads。


  強烈建議不要調用setmaxthreads方法修改線程池中線程數量的限制,因為這會導致損害應用程序的執行性能。


  clr的線程池試圖避免過快地創建額外的線程。具體而言,線程池試圖避免每隔500ms就創建一個新的線程。這對某些開發人 員而言,引發了一個問題,因為隊列中的任務無法得到及時地處理。要處理此問題,可以調用setminthreads方法設置線程池中擁有 線程的最低數量。調用該方法后,線程池將很快地創建這么多的線程,并且當隊列的任務繼續增加,所創建的所有線程都被使用后 ,線程池還會按照每隔500ms的時間繼續創建額外的線程。默認情況下,線程池中工作線程和i/o線程的最小數量被設為2,這個值可 以通過調用getminthreads方法獲得。


  最后,可以通過調用getavailablethreads方法來獲得線程池中可以增加的額外線程的數量。該方法的返回值為線程池中可 以擁有的線程的最大數量減去線程池中當前所擁有的線程數量。這個值僅在返回的那一刻有用,因為在方法返回后,線程池中可能 已經增加了許多線程,或有些線程可能已被銷毀。


使用線程池執行受計算限制的異步操作


  受計算限制的操作是需要進行計算的操作。如,電子表格應用程序中可計算的單元。理想情況下,受計算限制的操作不會執 行任何異步i/o操作,因為所有的異步i/o操作在底層硬件執行工作時都將掛起調用線程。應該盡量使線程運行,因為掛起的線程不 再繼續運行但仍然使用系統的資源。


  為了將一個受計算限制的異步操作加入到線程池的隊列中,一般可以使用threadpool類中定義的下述方法:

?

static bool QueueUserWorkItem(WaitCallback callback);
static bool QueueUserWorkItem(WaitCallback callback, object state);
static bool UnsafeQueueUserWorkItem(WaitCallback callback, object state);


  上述方法將一個“工作項”(及可選的狀態數據)加入到線程池的隊列中,然后這些方法就會立即返回。工作項僅僅是一個 由callback參數標識的方法,線程池中的線程將調用該方法。該方法可以只傳遞一個單獨的由state(狀態數據)參數指定的參數。 沒有state參數的QueueUserWorkItem方法為回調函數傳遞null。最終,線程池中的一些線程將執行工作項,從而導致我們的方法被 調用。我們寫的回調方法必須匹配system.threading.WaitCallback委托類型,它的定義方式如下所示:

delegate void WaitCallback(object state);


  下面的代碼演示了線程池中的線程如何異步調用一個方法:
?

using system;
using system.threading;

public static class program
{

public static void main()
{
console.writeline(
"main thread: queuing an asynchronous operation"); threadpool.QueueUserWorkItem(computeboundop, 5);
console.writeline(
"main thread: doing other work here ...");
thread.sleep(
10000); //模擬其他工作10秒鐘
console.writeline("hit <enter> to end this program ...");
console.readline(); }


//該方法的簽名必須與WaitCallback委托類型匹配
private static void computeboundop(object state)
{

//該方法由線程池中的線程執行
console.writeline("in computeboundop: state={0}", state);
thread.sleep(
1000); //模擬其他工作1秒鐘
//在該方法返回后,線程就回到線程池中,然后等待執行另一個任務
}
}


  如果回調方法拋出的異常是未處理異常,那么clr將終止進程。


  threadpool類有一個UnsafeQueueUserWorkItem方法。該方法與平時調用的QueueUserWorkItem方法非常相似。下面先簡單介 紹一下這兩個方法的區別:試圖訪問一個受限資源(如打開一個文件)時,clr將執行一個代碼訪問安全(code access security, cas)檢查。也就是說,clr將檢查調用線程的調用堆棧中的所有程序集是否都有訪問資源的許可權限。如果有一些程序集沒有所需 的許可權限,clr將拋出一個securityexception異常。假設正在執行代碼的線程所在的程序集沒有打開文件的許可權限,那么在線 程試圖打開文件時,clr將拋出一個securityexception異常。


  為讓線程繼續運行,線程可以在線程池的隊列加入一個工作項,讓線程池中的線程來執行打開文件的代碼。當然這必須在擁 有合適許可權限的程序集中進行。這種“工作區”智取安全權限的現象可以允許懷惡意的代碼對受限資源進行嚴重破壞。為阻止這 種獲得安全權限的方式,QueueUserWorkItem方法內部遍歷調用線程的堆棧,并捕獲所有被授予的安全權限。然后,當線程池中的線 程開始執行時,這些權限再與線程結合。因此,線程池中的線程以調用QueueUserWorkItem方法的線程相同的權限集來完成運行。


  遍歷線程的堆棧并捕獲所有的安全權限與性能緊密相關。如果希望改進受計算限制的異步操作的排隊性能,可以調用 UnsafeQueueUserWorkItem方法。該方法只將工作項加入到線程池的隊列中,而不遍歷調用線程的堆棧。最后結果是這個方法比 QueueUserWorkItem方法執行得快,但它在應用程序中打開了一個潛在的安全漏洞。僅當可以確認線程池中的線程執行的代碼不觸及 受限資源時,或確信接觸這部分資源不會出現問題時,我們才可以調用unsafequeueuserwork-item方法。同樣,還需注意調用該方 法需要使securitypermission的controlpolicy標記和controlevidence標記開啟,可阻止未信任的代碼偶然或故意提升它的許可權 限。


使用專用線程執行受計算限制的異步操作


  強烈建議大家盡量多用線程池來執行受計算限制的異步操作。但在有些情況下,我們可能希望顯式創建一個線程,專門用于 執行特定的受計算限制的異步操作。一般情況下,如果即將執行的代碼需要線程處于一個特定的狀態(與線程池中線程的普通狀態 不同),那么就希望創建一個專用的線程。如:希望線程以一個特殊的優先級運行(所有線程池中的線程都以普通優先級運行,而 且我們不應該修改線程池中線程的優先級),就需要創建一個專用的線程。再如:希望讓一個線程成為前臺線程(所有線程中的線 程都是后臺線程),也可以考慮創建并使用自己的線程,從而阻止應用程序的“死亡”,直到線程完成任務。如果受計算限制的任 務運行的時間特別長,也應該使用專用線程,這樣,我們就不必讓線程池的邏輯去費力判斷是否還需創建額外的線程。最后,如果 我們希望啟動一個線程,然后通過調用thread的abort方法中斷該線程的話,應該使用一個專用線程。


  為創建一個專用線程,我們可構建一個system.threading.thread類的實例(以方法的名稱作為構造器的參數)。下面是構 造器的原型:
?

public sealed class thread : criticalfinalizerobject, ...
{

public thread(parameterizedthreadstart start);
}


  參數start用來標識專用線程的方法即將執行,這個方法必須與委托parameterizedthreadstart的簽名相匹配:
?

delegate void parameterizedthreadstart(object obj);


  可看出,parameterizedthreadstart委托的簽名與WaitCallback委托的簽名相同。這意味著使用一個線程池中的線程或使用 一個專用線程就可以調用相同的方法。


  構建一個thread對象并不創建一個操作系統線程。為實際創建一個操作系統線程,并讓它開始執行回調方法,我們必須調用 thread的start方法。如下所示:
?

using system;
using system.threading;

public static class program
{

public static void main()
{
console.writeline(
"main thread: starting a dedicated thread " + " to do an asynchronous operation");
thread dedicatedthread
= new thread(computeboundop);
dedicatedthread.start(
5); console.writeline("main thread: doing other work here...");
thread.sleep(
10000); //模擬其他工作10秒 dedicatedthread.join(); //等待線程終止 console.writeline("hit <enter> to end this program...");
console.readline();
}







//該方法的簽名必須與parameterizedthreadstart委托匹配
private static void computeboundop(object state)
{

//該方法由一個專用線程執行
console.writeline("in computeboundop: state = {0}", state);
thread.sleep(
1000); //模擬其他工作1秒
}
}


  注意,main方法調用了join方法,而join方法導致調用線程停止執行任何代碼,直到由dedicatedthread標識的線程自己銷 毀自己或被終止。使用threadpool的QueueUserWorkItem方法將異步操作排隊時,clr沒有提供內置的方法來判斷操作是否完成。而 join方法卻在我們使用專用線程時為我們提供了這種能力。但是,如果需要知道操作是在什么時候完成的,就不應該使用專用線程 來取代QueueUserWorkItem方法,而應該使用apm。



轉載文章,C/S框架網責任編輯

?

總結

以上是生活随笔為你收集整理的C#.Net使用线程池(ThreadPool)与专用线程(Thread)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。