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

.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)

分享創造快樂