歡迎光臨
每天分享高質量文章

.NET各種池:字串拘留池、執行緒池 、應用程式池、資料庫連接池

來源:雪飛鴻

cnblogs.com/Cwj-XFH/p/8967876.html


.NET中,常用到的池有四個:字串拘留池、執行緒池 、應用程式池、資料庫連接池。


字串拘留池


在.NET中字串是不可變物件,修改字串變數的值會產生新的物件。為降低性能消耗及減小程式集大小,.NET提供了string interning的功能,直譯過來就是字串拘留。


所謂的字串拘留池(intern pool)其實是一張哈希表,鍵是字串字面量,值是托管堆上字串物件的取用。但若該表過大,則會對性能造成負面影響。


在加載程式集時,不同版本的CLR對於是否留用程式集元資料中的字串字面量(在編譯時值已確定)不盡相同。但顯式呼叫string.Intern方法則會將字串字面量放入池中。


我們在給string型別變數分配字面量值時,CLR會先到字串池中看下有沒有完全相同的字串(區分大小寫),若有則傳回對應的取用,若無,則創建新物件並添加到字串池中傳回取用。但若在運行時(如,使用new關鍵字)來給字串變數分配值則不會使用字串池。


C#提供了和字串池相關的兩個方法:


//若str不在字串池中就創建新字串物件放到池裡並傳回取用

public staticc String Intern(String str);

//若str不在字串池中不會創建新字串物件並傳回null

public staticc String IsInterned(String str);


示例代碼如下:


var str = “abc”;

var str01 = “abc”;

//運行時常量

var str02 = new string(new char[] { ‘a’, ‘b’, ‘c’ });

//編譯時常量(可通過反編譯器查看編譯後的代碼)

string str03 = “a” + “bc”;


Console.WriteLine($”str01==str is {ReferenceEquals(str01, str)}”);

Console.WriteLine($”str02==str is {ReferenceEquals(str02, str)}”);

Console.WriteLine($”str03==str is {ReferenceEquals(str03, str)}”);


var str04 = String.IsInterned(new string(new char[] { ‘a’, ‘b’ }));

Console.WriteLine($”str04 == null is {str04 == null}”);

var str05 = String.IsInterned(“abdgj”);

Console.WriteLine($”str05={str05}”);


var str06 = String.Intern(new string(new char[] { ‘a’, ‘b’, ‘d’, ‘e’ }));

Console.WriteLine($”str06={str06}”);


得到如下結果:



執行緒池


一個行程中只有一個執行緒池(MSDN)。另一種說法是,一個CLR中一個執行緒池(《CLR via C#》),我認同這種說法。


一個行程可以加載多個不同版本的CLR,但同一版本的CLR只能有一個。總之,執行緒不屬於應用程式域(AppDomain)。


若執行緒池中的執行緒存在未處理的異常,則會導致當前行程被終止,但有三個例外:


  • ThreadAbortException ,在呼叫 Abort 方法終止執行緒時會丟擲該異常


  • AppDomainUnloadedException ,在卸載AppDomain時會丟擲該異常


  • CLR或宿主行程終止一個執行緒時


在.NET1.0和1.1版本中, CLR會處理掉執行緒池中未處理的異常。但這樣做會破壞應用程式中的狀態甚至導致程式掛起,這些不利於除錯。


在.NET中,許多場景可以使用執行緒池。如,異步I/O,回呼,註冊wait操作,使用委托的異步方法呼叫及System.Net 中的socket連接。


但在如下場景中應避免使用執行緒池中的執行緒:


  • 需要使用前臺執行緒時


  • 執行緒需要特定優先級時


  • 需要執行比較耗時的操作時。因為執行緒池中的執行緒數有上限,因此長時間的阻塞可能會影響其它任務的處理


  • 當需要放置執行緒在單執行緒單元(single-threaded apartment)時。執行緒池中的執行緒均在多執行緒單元(multithreaded apartment)中


  • 需要給執行緒一個穩定的標識或者執行緒用於特定任務時


執行緒池中的執行緒分為兩種:工作執行緒(Worker)和I/O執行緒(I/O Completion Port)。這兩種執行緒只是用處不同,並無本質區別。


執行緒池中的最小執行緒數預設為處理器的邏輯核心數。即,在4核計算機上,執行緒池中工作執行緒和I/O執行緒預設的最小數均為4。理論上,執行緒池中的最大執行緒數只受可用記憶體大小限制,但是執行緒池會限制行程內可用執行緒的數量。


ThreadPool.GetMinThreads(out var minWorkerThreadCount, out var minIoThreadCount);

Console.WriteLine($”minWorkerThreadCount={minWorkerThreadCount},minIoThreadCount={minIoThreadCount}”);

ThreadPool.GetMaxThreads(out var maxWorkerThreadCount, out var maxIoThreadCount);

Console.WriteLine($”maxWorkerThreadCount={maxWorkerThreadCount},maxIoThreadCount={maxIoThreadCount}”);


運算結果如下:



當應用使用執行緒池中的執行緒進行工作時,若執行緒池中沒有執行緒,則會創建新的執行緒以滿足需要,當執行緒池中的執行緒數達到設定的最小執行緒數且無空閑執行緒時,則會先等待一段時間(最多500ms),500ms過後依然沒有空閑執行緒可供使用則會創建新執行緒進行工作,但執行緒池中的執行緒數不會超過設定的最大執行緒數。


當執行緒池中的執行緒處於空閑狀態一段時間後(不同CLR,這個時間不同),會被銷毀。


當應用負載較低時,執行緒池中的執行緒數也有可能小於設定的最小執行緒數。


machine.config中執行緒池配置如下(.NET 配置檔案體系參見:ASP.NET Configuration File Hierarchy and Inheritance):


     


配置執行緒池大小:


//這種配置方式和處理CPU邏輯核心數無關

ThreadPool.SetMaxThreads(1000, 800);

ThreadPool.SetMinThreads(20, 20);


ASP.NET也可通過配置檔案進行配置,這種方式是針對每個CPU邏輯核心進行配置:


 

   

 


這樣做,在應用啟動後會報錯:在 machine.config 檔案之外使用註冊為 allowDefinition=’MachineOnly’ 的節是錯誤的。需要修改machine.config檔案。


執行緒池配置得當對於應用性能提升是有不少幫助的。


應用程式池


IIS5中,一臺服務器只有一個工作行程,不同應用使用AppDomain進行區分,當工作行程出現問題,所有應用都會受到影響。


從IIS6開始引入了應用程式池的概念,應用程式池通過行程來隔離不同的應用程式以防止不同應用之間相互影響。在部署ASP.NET應用時,應用程式池通常有兩種托管管道樣式可供選擇:集成樣式和經典樣式。


預設情況下,一個應用程式池有一個工作行程,可以根據實際情況設置多個工作行程,但要考慮資源消耗及本地快取同步問題。


IIS6和IIS5中的工作行程隔離均是在服務器級別。在同一臺服務器上無法使用不同的工作行程隔離樣式。從IIS7開始,工作行程隔離樣式是基於應用程式池的,這樣就可以在同一臺服務器上使用不同的隔離樣式。


在應用程式池——高級設置中可以對應用程式池做相關設置,如佇列長度,工作行程回收機制等。


 


資料庫連接池


和資料庫服務器建立連接的過程是比較耗時的,對此,ADO.NET中使用了連接池來進行優化。


在.NET中不同的Data Provider對於連接池的處理方式不盡相同。


預設情況下,ADO.NET 啟用連接池優化,可以通過連接字串來配置是否啟用連接池。


連接池可以減少和資料庫建立連接的次數,連接池中維護著一組活躍的資料庫連接。在我們呼叫IDbConnection的Open方法時,CLR會去連接池中尋找是否有可用的連接,若有則傳回該連接而無需與資料庫建立新的連接。


當我們呼叫IDbConnection的Close方法時,連接會被連接池回收但不斷開與資料庫的連接,以備下次使用。連接池中的連接空閑一段時間(約4~8分鐘)後或者連接池檢測到連接已與服務器斷開(需要與服務器通訊才能檢測連接是否已斷開),那麼該連接將會被銷毀。


在第一次打開連接時,ADO.NET會根據連接配置來建立連接池。ADO.NET為每個連接配置創建一個連接池,所以若程式中用到多個不同的連接配置(如,不同的連接字串),則會有多個連接池。


若連接池中發生了超時或者其它登錄錯誤,則會丟擲異常,那麼在接下來的5s內嘗試該連接都將失敗,這5s鐘成為阻塞期。若阻塞期結束後的連接再次失敗,則會進入一個新的阻塞期,新的阻塞期時長是上個阻塞期時長的2倍,但最多不超過1分鐘。


如果連接字串中沒有設置MinPoolSize的值,或者將該值設為0,那麼當池中沒有活動連接時,連接池也會被銷毀。但若將MinPoolSize的值設為大於0,那麼只有在卸載AppDomain時,連接池才會被銷毀。當連接池中發生了較為嚴重的錯誤,連接池也會自我清理。


連接池中最大連接數預設為100,當連接池中連接數已達到上限,且均被占用,那麼新的請求會進入佇列等到,等待時間超過15s(預設)則會丟擲異常。


資料庫連接推薦使用如下寫法,這樣using陳述句結束後,連接物件會回到連接池中以便下次請求使用。


using (IDbConnection conn = new SqlConnection())

{


}


結語


以上,是本人學習的一點兒心得,錯誤之處望大家多多指教。


編號124,輸入編號直達本文

●輸入m獲取到文章目錄

推薦↓↓↓

 

資料庫開發

更多推薦18個技術類公眾微信

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂