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

若线程池中的线程存在未处理的异常,则会导致当前进程被终止,但有三个例外:

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

<system.web>
<processModel autoConfig="true"/>
</system.web>

配置线程池大小:

//这种配置方式和处理CPU逻辑核心数无关
ThreadPool.SetMaxThreads(, );
ThreadPool.SetMinThreads(, );

ASP.NET也可通过配置文件进行配置,这种方式是针对每个CPU逻辑核心进行配置:

<configuration>
<system.web>
<processModel minWorkerThreads="20" minIoThreads="20" />
</system.web>
</configuration>

这样做,在应用启动后会报错:在 machine.config 文件之外使用注册为 allowDefinition='MachineOnly' 的节是错误的。需要修改machine.config文件。

线程池配置得当对于应用性能提升是有不少帮助的

应用程序池

IIS5中,一台服务器只有一个工作进程,不同应用使用AppDomain进行区分,当工作进程出现问题,所有应用都会受到影响。从IIS6开始引入了应用程序池的概念,应用程序池通过进程来隔离不同的应用程序以防止不同应用之间相互影响。在部署ASP.NET应用时,应用程序池通常有两种托管管道模式可供选择:集成模式和经典模式

默认情况下,一个应用程序池有一个工作进程,可以根据实际情况设置多个工作进程,但要考虑资源消耗及本地缓存同步问题。

IIS6和IIS5中的工作进程隔离均是在服务器级别。在同一台服务器上无法使用不同的工作进程隔离模式。从IIS7开始,工作进程隔离模式是基于应用程序池的,这样就可以在同一台服务器上使用不同的隔离模式。

在应用程序池——高级设置中可以对应用程序池做相关设置,如队列长度,工作进程回收机制等。

 
 

数据库连接池

和数据库服务器建立连接的过程是比较耗时的,对此,ADO.NET中使用了连接池来进行优化。在.NET中不同的Data Provider对于连接池的处理方式不尽相同。默认情况下,ADO.NET 启用连接池优化,可以通过连接字符串来配置是否启用连接池。

连接池可以减少和数据库建立连接的次数,连接池中维护着一组活跃的数据库连接。在我们调用IDbConnectionOpen方法时,CLR会去连接池中寻找是否有可用的连接,若有则返回该连接而无需与数据库建立新的连接。当我们调用IDbConnectionClose方法时,连接会被连接池回收但不断开与数据库的连接,以备下次使用。连接池中的连接空闲一段时间(约4~8分钟)后或者连接池检测到连接已与服务器断开(需要与服务器通讯才能检测连接是否已断开),那么该连接将会被销毁。

在第一次打开连接时,ADO.NET会根据连接配置来建立连接池。ADO.NET为每个连接配置创建一个连接池,所以若程序中用到多个不同的连接配置(如,不同的连接字符串),则会有多个连接池。

若连接池中发生了超时或者其它登录错误,则会抛出异常,那么在接下来的5s内尝试该连接都将失败,这5s钟成为阻塞期。若阻塞期结束后的连接再次失败,则会进入一个新的阻塞期,新的阻塞期时长是上个阻塞期时长的2倍,但最多不超过1分钟。

如果连接字符串中没有设置MinPoolSize的值,或者将该值设为0,那么当池中没有活动连接时,连接池也会被销毁。但若将MinPoolSize的值设为大于0,那么只有在卸载AppDomain时,连接池才会被销毁。当连接池中发生了较为严重的错误,连接池也会自我清理。

连接池中最大连接数默认为100,当连接池中连接数已达到上限,且均被占用,那么新的请求会进入队列等到,等待时间超过15s(默认)则会抛出异常。

数据库连接推荐使用如下写法,这样using语句结束后,连接对象会回到连接池中以便下次请求使用。

using (IDbConnection conn = new SqlConnection())
{ }

除了客户端维持的连接池外,数据库服务本身还有连接数的限制

 

我将数据库最大并发连接数设为1,ADO.NET连接池使用默认配置,循环打开3000个连接并未发生异常:

for (var i = ; i < ; i++)
{
var conn = new SqlConnection(connStr);
conn.Open();
}

但若将连接池最大连接数设置成一个比较小的数,如3,再次执行上述代码则会发生异常:

 

按这种情况看,连接池中的连接公用了同一个数据库连接。在连接池连接数耗尽时,则因为等待超时而抛出异常。

但我并没有悟出数据库最大并发连接数的奥妙,哪位童鞋知道,麻烦告知,在此表示感谢!

结语

以上,是本人学习的一点儿心得,错误之处望大家多多指教。

推荐阅读

Thread Pool
Exceptions in Managed Threads.
StackExchange.Redis Timeout
记5.28大促压测的性能优化—线程池相关问题(线程池配置不当导致)
工作者线程(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最大连接数和队列长度
System.Threading.Tasks.Task引起的IIS应用程序池崩溃
HTTP.SYS 详解
IIS执行原理
IIS ASP.NET的进程模式浅析


SQL Server Connection Pooling (ADO.NET)
Connection Pooling

SqlServer数据库连接数与客户端连接池关系测试(一)

版权声明

本文为作者原创,版权归作者雪飞鸿所有。 转载必须保留文章的完整性,且在页面明显位置处标明原文链接

如有问题, 请发送邮件和作者联系。

.NET中的各种池的更多相关文章

  1. android中的线程池学习笔记

    阅读书籍: Android开发艺术探索 Android开发进阶从小工到专家 对线程池原理的简单理解: 创建多个线程并且进行管理,提交的任务会被线程池指派给其中的线程进行执行,通过线程池的统一调度和管理 ...

  2. node-mysql中的连接池代码学习

    node-mysql是一个node.js下的mysql驱动,前段时间在处理连接池的问题上遇到了连接不释放的疑难杂症,虽已解决,但仍需总结经验避免下次重蹈覆辙.下面是node-mysql中的连接池的部分 ...

  3. mongoDB中的连接池(转载)

    一.mongoDB中的连接池 刚上手MongoDB,在做应用时,受以前使用关系型数据库的影响,会考虑数据库连接池的问题! 关系型数据库中,我们做连接池无非就是事先建立好N个连接(connection) ...

  4. Java5中的线程池实例讲解

    Java5增加了新的类库并发集java.util.concurrent,该类库为并发程序提供了丰富的API多线程编程在Java 5中更加容易,灵活.本文通过一个网络服务器模型,来实践Java5的多线程 ...

  5. Jsp中使用数据库连接池.

    原文 Jsp中使用数据库连接池. 1. 在tomcat服务器目录下面的conf中找到一个叫Context.xml的配置文件,在其中加入以下代码 <Resource name="jdbc ...

  6. Spring中的线程池和定时任务功能

    1.功能介绍 Spring框架提供了线程池和定时任务执行的抽象接口:TaskExecutor和TaskScheduler来支持异步执行任务和定时执行任务功能.同时使用框架自己定义的抽象接口来屏蔽掉底层 ...

  7. python中的进程池

    1.进程池的概念 python中,进程池内部会维护一个进程序列.当需要时,程序会去进程池中获取一个进程. 如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止. 2.进程池 ...

  8. python中的进程池:multiprocessing.Pool()

    python中的进程池: 我们可以写出自己希望进程帮助我们完成的任务,然后把任务批量交给进程池 进程池帮助我们创建进程完成任务,不需要我们管理.进程池:利用multiprocessing 下的Pool ...

  9. jdk1.7中的常量池

    在探究jdk1.7中的常量池,我们可以先看看以下的这段代码 public static void main(String[] args) throws Throwable { List<Stri ...

随机推荐

  1. 人工智能算法综述(二) RNN and LSTM

    接上一篇 :AI算法综述 (一) RNN:循环神经网络 and LSTM 长短期记忆网络 LSTM就是一个RNN网络,外部的结构是一样的,主要是单元的内在结构不同.或者说LSTM是为了让RNN能够更好 ...

  2. Python中字符串颜色

    格式:\033[显示方式;前景色;背景色m 说明: 前景色 背景色 颜色 --------------------------------------- 30 40 黑色 31 41 红色 32 42 ...

  3. hive:默认允许动态分区个数为100,超出抛出异常:

    在创建好一个分区表后,执行动态分区插入数据,抛出了错误: Caused by: org.apache.hadoop.hive.ql.metadata.HiveFatalException: [Erro ...

  4. C# 类型、存储和变量

    如果广泛地描述C和C++程序的源代码的特征,可以说C程序是一组函数和数据类型,C++程序是一组函数和类,然而C#程序是一组类型声明. 既然C#程序就是一组类型声明,那么学习C#就是学习如何创建和使用类 ...

  5. java--- 使用interrupte中断线程的真正用途

    Java线程之中,一个线程的生命周期分为:初始.就绪.运行.阻塞以及结束.当然,其中也可以有四种状态,初始.就绪.运行以及结束. 一般而言,可能有三种原因引起阻塞:等待阻塞.同步阻塞以及其他阻塞(睡眠 ...

  6. hive新特性reflect函数介绍

    reflect函数可以支持在sql中调用java中的自带函数,秒杀一切udf函数. 使用案例1:所有记录执行相同的java内置函数 hive中建一张表test_udf:column1(int),col ...

  7. ubuntu下安装foxi reader阅读器

    参考 :https://blog.csdn.net/u013831198/article/details/72472040 请参照以下步骤安装Foxit Reader(适用于Linux):   •   ...

  8. [LeetCode] Palindromic Substrings 回文子字符串

    Given a string, your task is to count how many palindromic substrings in this string. The substrings ...

  9. mysql5.5以上 用户的操作

    mysql5.5以上 用户的操作(转)   1.创建用户 create user 'username'@'host' identified by 'password'; 参数说明: username: ...

  10. 11_Python的列表推导式_Python编程之路

    上一节我们结束了有关python的方法相关内容 这一节我们先来学习python的列表推导式 之前跟大家说过range方法的使用,在这里我就不做过多的解释了,如忘记了,请翻阅之前的介绍 列表推导式,是P ...