在.NET中,常用到的池有四個:字符串拘留池提揍、線程池 、應(yīng)用程序池煮仇、數(shù)據(jù)庫連接池劳跃。
字符串拘留池
在.NET中字符串是不可變對象,修改字符串變量的值會產(chǎn)生新的對象欺抗。為降低性能消耗及減小程序集大小售碳,.NET提供了string interning的功能,直譯過來就是字符串拘留绞呈。所謂的字符串拘留池(intern pool)其實是一張哈希表贸人,鍵是字符串字面量,值是托管堆上字符串對象的引用佃声。但若該表過大艺智,則會對性能造成負(fù)面影響。在加載程序集時圾亏,不同版本的CLR對于是否留用程序集元數(shù)據(jù)中的字符串字面量(在編譯時值已確定)不盡相同十拣。但顯式調(diào)用string.Intern
方法則會將字符串字面量放入池中。
我們在給string類型變量分配字面量值時志鹃,CLR會先到字符串池中看下有沒有完全相同的字符串(區(qū)分大小寫)夭问,若有則返回對應(yīng)的引用,若無曹铃,則創(chuàng)建新對象并添加到字符串池中返回引用缰趋。但若在運(yùn)行時(如,使用new關(guān)鍵字)來給字符串變量分配值則不會使用字符串池。
C#提供了和字符串池相關(guān)的兩個方法:
//若str不在字符串池中就創(chuàng)建新字符串對象放到池里并返回引用
public staticc String Intern(String str);
//若str不在字符串池中不會創(chuàng)建新字符串對象并返回null
public staticc String IsInterned(String str);
示例代碼如下:
var str = "abc";
var str01 = "abc";
//運(yùn)行時常量
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}");
得到如下結(jié)果:
線程池
一個進(jìn)程中只有一個線程池(MSDN)秘血。另一種說法是味抖,一個CLR中一個線程池(《CLR via C#》),我認(rèn)同這種說法灰粮。一個進(jìn)程可以加載多個不同版本的CLR仔涩,但同一版本的CLR只能有一個≌持郏總之熔脂,線程不屬于應(yīng)用程序域(AppDomain)。
若線程池中的線程存在未處理的異常蓖乘,則會導(dǎo)致當(dāng)前進(jìn)程被終止锤悄,但有三個例外:
ThreadAbortException ,在調(diào)用 Abort 方法終止線程時會拋出該異常
AppDomainUnloadedException 嘉抒,在卸載AppDomain時會拋出該異常
CLR或宿主進(jìn)程終止一個線程時
在.NET1.0和1.1版本中, CLR會處理掉線程池中未處理的異常零聚。但這樣做會破壞應(yīng)用程序中的狀態(tài)甚至導(dǎo)致程序掛起,這些不利于調(diào)試些侍。
在.NET中隶症,許多場景可以使用線程池。如岗宣,異步I/O蚂会,回調(diào),注冊wait操作耗式,使用委托的異步方法調(diào)用及System.Net 中的socket連接胁住。
但在如下場景中應(yīng)避免使用線程池中的線程:
- 需要使用前臺線程時
- 線程需要特定優(yōu)先級時
- 需要執(zhí)行比較耗時的操作時。因為線程池中的線程數(shù)有上限刊咳,因此長時間的阻塞可能會影響其它任務(wù)的處理
- 當(dāng)需要放置線程在單線程單元(single-threaded apartment)時彪见。線程池中的線程均在多線程單元(multithreaded apartment)中
- 需要給線程一個穩(wěn)定的標(biāo)識或者線程用于特定任務(wù)時
線程池中的線程分為兩種:工作線程(Worker)和I/O線程(I/O Completion Port)。這兩種線程只是用處不同娱挨,并無本質(zhì)區(qū)別余指。
線程池中的最小線程數(shù)默認(rèn)為處理器的邏輯核心數(shù)。即跷坝,在4核計算機(jī)上酵镜,線程池中工作線程和I/O線程默認(rèn)的最小數(shù)均為4。理論上柴钻,線程池中的最大線程數(shù)只受可用內(nèi)存大小限制淮韭,但是線程池會限制進(jìn)程內(nèi)可用線程的數(shù)量。
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}");
運(yùn)算結(jié)果如下:
當(dāng)應(yīng)用使用線程池中的線程進(jìn)行工作時贴届,若線程池中沒有線程靠粪,則會創(chuàng)建新的線程以滿足需要足丢,當(dāng)線程池中的線程數(shù)達(dá)到設(shè)定的最小線程數(shù)且無空閑線程時,則會先等待一段時間(最多500ms)庇配,500ms過后依然沒有空閑線程可供使用則會創(chuàng)建新線程進(jìn)行工作,但線程池中的線程數(shù)不會超過設(shè)定的最大線程數(shù)绍些。
當(dāng)線程池中的線程處于空閑狀態(tài)一段時間后(不同CLR捞慌,這個時間不同),會被銷毀柬批。
當(dāng)應(yīng)用負(fù)載較低時啸澡,線程池中的線程數(shù)也有可能小于設(shè)定的最小線程數(shù)。
machine.config
中線程池配置如下(.NET 配置文件體系參見:ASP.NET Configuration File Hierarchy and Inheritance):
<system.web>
<processModel autoConfig="true"/>
</system.web>
配置線程池大械省:
//這種配置方式和處理CPU邏輯核心數(shù)無關(guān)
ThreadPool.SetMaxThreads(1000, 800);
ThreadPool.SetMinThreads(20, 20);
ASP.NET也可通過配置文件進(jìn)行配置嗅虏,這種方式是針對每個CPU邏輯核心進(jìn)行配置:
<configuration>
<system.web>
<processModel minWorkerThreads="20" minIoThreads="20" />
</system.web>
</configuration>
這樣做,在應(yīng)用啟動后會報錯:在 machine.config 文件之外使用注冊為 allowDefinition='MachineOnly' 的節(jié)是錯誤的上沐。
需要修改machine.config
文件皮服。
線程池配置得當(dāng)對于應(yīng)用性能提升是有不少幫助的。
應(yīng)用程序池
IIS5中参咙,一臺服務(wù)器只有一個工作進(jìn)程龄广,不同應(yīng)用使用AppDomain進(jìn)行區(qū)分,當(dāng)工作進(jìn)程出現(xiàn)問題蕴侧,所有應(yīng)用都會受到影響择同。從IIS6開始引入了應(yīng)用程序池的概念,應(yīng)用程序池通過進(jìn)程來隔離不同的應(yīng)用程序以防止不同應(yīng)用之間相互影響净宵。在部署ASP.NET應(yīng)用時敲才,應(yīng)用程序池通常有兩種托管管道模式可供選擇:集成模式和經(jīng)典模式。
默認(rèn)情況下择葡,一個應(yīng)用程序池有一個工作進(jìn)程紧武,可以根據(jù)實際情況設(shè)置多個工作進(jìn)程,但要考慮資源消耗及本地緩存同步問題刁岸。
IIS6和IIS5中的工作進(jìn)程隔離均是在服務(wù)器級別缚陷。在同一臺服務(wù)器上無法使用不同的工作進(jìn)程隔離模式处面。從IIS7開始,工作進(jìn)程隔離模式是基于應(yīng)用程序池的,這樣就可以在同一臺服務(wù)器上使用不同的隔離模式祝旷。
在應(yīng)用程序池——高級設(shè)置中可以對應(yīng)用程序池做相關(guān)設(shè)置,如隊列長度贪婉,工作進(jìn)程回收機(jī)制等户辱。
數(shù)據(jù)庫連接池
和數(shù)據(jù)庫服務(wù)器建立連接的過程是比較耗時的,對此疏哗,ADO.NET中使用了連接池來進(jìn)行優(yōu)化呛讲。在.NET中不同的Data Provider對于連接池的處理方式不盡相同。默認(rèn)情況下,ADO.NET 啟用連接池優(yōu)化贝搁,可以通過連接字符串來配置是否啟用連接池吗氏。
連接池可以減少和數(shù)據(jù)庫建立連接的次數(shù),連接池中維護(hù)著一組活躍的數(shù)據(jù)庫連接雷逆。在我們調(diào)用IDbConnection
的Open
方法時弦讽,CLR會去連接池中尋找是否有可用的連接,若有則返回該連接而無需與數(shù)據(jù)庫建立新的連接膀哲。當(dāng)我們調(diào)用IDbConnection
的Close
方法時往产,連接會被連接池回收但不斷開與數(shù)據(jù)庫的連接,以備下次使用某宪。連接池中的連接空閑一段時間(約4~8分鐘)后或者連接池檢測到連接已與服務(wù)器斷開(需要與服務(wù)器通訊才能檢測連接是否已斷開)仿村,那么該連接將會被銷毀。
在第一次打開連接時兴喂,ADO.NET會根據(jù)連接配置來建立連接池蔼囊。ADO.NET為每個連接配置創(chuàng)建一個連接池,所以若程序中用到多個不同的連接配置(如瞻想,不同的連接字符串)压真,則會有多個連接池。
若連接池中發(fā)生了超時或者其它登錄錯誤蘑险,則會拋出異常滴肿,那么在接下來的5s內(nèi)嘗試該連接都將失敗,這5s鐘成為阻塞期佃迄。若阻塞期結(jié)束后的連接再次失敗泼差,則會進(jìn)入一個新的阻塞期,新的阻塞期時長是上個阻塞期時長的2倍呵俏,但最多不超過1分鐘堆缘。
如果連接字符串中沒有設(shè)置MinPoolSize
的值,或者將該值設(shè)為0普碎,那么當(dāng)池中沒有活動連接時吼肥,連接池也會被銷毀。但若將MinPoolSize
的值設(shè)為大于0麻车,那么只有在卸載AppDomain時缀皱,連接池才會被銷毀。當(dāng)連接池中發(fā)生了較為嚴(yán)重的錯誤动猬,連接池也會自我清理啤斗。
連接池中最大連接數(shù)默認(rèn)為100,當(dāng)連接池中連接數(shù)已達(dá)到上限赁咙,且均被占用钮莲,那么新的請求會進(jìn)入隊列等到免钻,等待時間超過15s(默認(rèn))則會拋出異常。
數(shù)據(jù)庫連接推薦使用如下寫法崔拥,這樣using
語句結(jié)束后极舔,連接對象會回到連接池中以便下次請求使用。
using (IDbConnection conn = new SqlConnection())
{
}
除了客戶端維持的連接池外链瓦,數(shù)據(jù)庫服務(wù)本身還有連接數(shù)的限制:
我將數(shù)據(jù)庫最大并發(fā)連接數(shù)設(shè)為1姆怪,ADO.NET連接池使用默認(rèn)配置,循環(huán)打開3000個連接并未發(fā)生異常:
for (var i = 0; i < 3000; i++)
{
var conn = new SqlConnection(connStr);
conn.Open();
}
但若將連接池最大連接數(shù)設(shè)置成一個比較小的數(shù)澡绩,如3,再次執(zhí)行上述代碼則會發(fā)生異常:
按這種情況看俺附,連接池中的連接公用了同一個數(shù)據(jù)庫連接肥卡。在連接池連接數(shù)耗盡時,則因為等待超時而拋出異常事镣。
但我并沒有悟出數(shù)據(jù)庫最大并發(fā)連接數(shù)的奧妙步鉴,哪位童鞋知道,麻煩告知璃哟,在此表示感謝氛琢!
結(jié)語
以上,是本人學(xué)習(xí)的一點兒心得随闪,錯誤之處望大家多多指教阳似。
推薦閱讀
Thread Pool
Exceptions in Managed Threads.
StackExchange.Redis Timeout
記5.28大促壓測的性能優(yōu)化—線程池相關(guān)問題(線程池配置不當(dāng)導(dǎo)致)
工作者線程(worker thread)和I/O線程
Introduction to IIS Architectures
ASP.NET Integration with IIS 7
ASP.NET Configuration File Hierarchy and Inheritance
IIS與ASP.NET中的線程池
iis最大連接數(shù)和隊列長度
System.Threading.Tasks.Task引起的IIS應(yīng)用程序池崩潰
HTTP.SYS 詳解
IIS執(zhí)行原理
IIS ASP.NET的進(jìn)程模式淺析
SQL Server Connection Pooling (ADO.NET)
Connection Pooling
SqlServer數(shù)據(jù)庫連接數(shù)與客戶端連接池關(guān)系測試(一)