线程池(ThreadPool)

线程池概述

由系统保险的容纳线程的器皿,由CLR控制的兼具AppDomain共享。线程池可用以实践任务、发送工作项、处理异步
I/O、代表任何线程等待以及处理计时器。

 

线程池与线程

性能:每开启一个新的线程都要消耗内存空间及资源(默许意况下大概1
MB的内存),同时四线程情状下操作系统必须调度可运行的线程并执行上下文切换,所以太多的线程还对质量不利。而线程池其目标是为了减小开启新线程消耗的资源(使用线程池中的空闲线程,不必再打开新线程,以及联合管理线程(线程池中的线程执行落成后,回归到线程池内,等待新职分))。

时间:无论哪一天启动一个线程,都亟需时日(几百阿秒),用于成立新的一些变量堆,线程池预先制造了一组可回收线程,因而得以减弱过载时间。

线程池缺点:线程池的品质损耗优于线程(通过共享和回收线程的点子完成),然则:

1.线程池不匡助线程的吊销、达成、战败布告等交互性操作。

2.线程池不扶助线程执行的主次次序排序。

3.不能够安装池化线程(线程池内的线程)的Name,会追加代码调试难度。

4.池化线程日常都是后台线程,优先级为ThreadPriority.诺玛l。

5.池化线程阻塞会潜移默化属性(阻塞会使CLR错误地认为它占用了多量CPU。CLR可以检测或补给(往池中流入越来越多线程),但是这或者使线程池受到持续超负荷的回忆。Task解决了那几个标题)。

6.线程池使用的是大局队列,全局队列中的线程依旧会存在竞争共享资源的图景,从而影响属性(Task解决了那些题目方案是使用当地队列)。

 

线程池工作规律

CLR初阶化时,线程池中是尚未线程的。在其间,线程池维护了一个操作请求队列。应用程序执行一个异步操作时,会将一个记录项增添到线程池的队列中。线程池的代码从那个行列中读取记录将以此记录项派发给一个线程池线程。倘诺线程池没有线程,就创办一个新线程。当线程池线程完毕工作后,线程不会被灭绝,相反线程会重回线程池,在这边进入空闲状态,等待响应另一个呼吁,由于线程不销毁自身,所以不再爆发额外的属性损耗。

程序向线程池发送多条请求,线程池尝试只用那么些线程来服务具有请求,当呼吁速度当先线程池线程处理义务速度,就会创制额外线程,所以线程池不必创立大气线程。

假诺悬停向线程池发送职分,池中多量空闲线程将在一段时间后自己醒来终止自己以自由资源(CLR不一致版本对那一个事件定义不一)。

 

劳力线程&I/O线程

线程池允许线程在八个CPU内核上调度职责,使七个线程能并发工作,从而高效能的运用系统资源,提高程序的吞吐性。

CLR线程池分为工作者线程与I/O线程二种:

劳力线程(workerThreads):负责管理CLR内部对象的运行,提供”运算能力“,所以普通用于总结密集(compute-bound)性操作。

I/O线程(completionPortThreads):主要用来与外表系统互换音信(如读取一个文本)和散发IOCP中的回调。

专注:线程池会预先缓存一些劳力线程因为制造新线程的代价相比较昂贵。

 

IO已毕端口(IOCP)

IO完结端口(IOCP、I/O completion
port)
:IOCP是一个异步I/O的API(可以视作一个新闻队列),提供了拍卖多少个异步I/O请求的线程模型,它可以火速地将I/O事件通报给应用程序。IOCP由CLR内部维护,当异步IO请求完结时,设备驱动就会扭转一个I/O请求包(IRP、I/O
Request
Packet)
,并排队(先入先出)放入完结端口。之后会由I/O线程提取落成IRP并调用从前的委托。

I/O线程&IOCP&IRP:

当执行I/O操作时(同步I/O操作 and
异步I/O操作),都会调用Windows的API方法将眼前的线程从用户态转变成内核态,同时生成并先导化一个I/O请求包,请求包中富含一个文本句柄,一个偏移量和一个Byte[]数组。I/O操作向基础传递请求包,依照这些请求包,windows内核确认那一个I/O操作对应的是哪个硬件装备。那个I/O操作会进入设备自己的拍卖队列中,该队列由那几个装置的驱动程序维护。

即使是同步I/O操作,那么在硬件装置操作I/O的时候,发出I/O请求的线程由于”等待“(无人职分处理)被Windows变成睡眠情状,当硬件装备落成操作后,再唤醒那个线程。所以品质不高,如若请求数广大,那么休眠的线程数也很多,浪费多量资源。

如如若异步I/O操作(在.Net中,异步的I/O操作都是以Beginxxx格局早先,内部贯彻为ThreadPool.BindHandle,须求传入一个信托,该委托会随着IRP一路传递到设备的驱动程序),该方法在Windows把I/O请求包发送到设备的处理队列后就会回去。同时,CLR会分配一个可用的线程用于继续执行接下去的职务,当职责到位后,通过IOCP提示CLR它工作已经已毕,当收到到通报后将该信托再放到CLR线程池队列中由I\O线程进行回调。

因此:大部分气象下,开发人士使用劳力线程,I/O线程由CLR调用(开发者并不会一贯运用)。

 

基础线程池&工作者线程(ThreadPool)

.NET中运用线程池用到ThreadPool类,ThreadPool是一个静态类,定义于System.Threading命名空间,自.NET
1.1起引入。

调用方法QueueUserWorkItem可以将一个异步的臆想范围操作放到线程池的连串中,这些措施向线程池的行列添加一个做事项以及可选的景色数据。
工作项:由callBack参数标识的一个主意,该措施由线程池线程调用。可向方法传递一个state实参(多于一个参数则须要封装为实体类)。

1  public static bool QueueUserWorkItem(WaitCallback callBack);
2  public static bool QueueUserWorkItem(WaitCallback callBack, object state);

 上边是经过QueueUserWorkItem启动劳引力线程的示例:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //方式一
 6             {
 7                 ThreadPool.QueueUserWorkItem(n => Test("Test-ok"));
 8             }
 9             //方式二
10             {
11                 WaitCallback waitCallback = new WaitCallback(Test);
12                 ThreadPool.QueueUserWorkItem(n => waitCallback("WaitCallback"));//两者效果相同 ThreadPool.QueueUserWorkItem(waitCallback,"Test-ok");
13             }
14             //方式三
15             {
16                 ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(Test);
17                 ThreadPool.QueueUserWorkItem(n => parameterizedThreadStart("ParameterizedThreadStart"));
18             }
19             //方式四
20             {
21                 TimerCallback timerCallback = new TimerCallback(Test);
22                 ThreadPool.QueueUserWorkItem(n => timerCallback("TimerCallback"));
23             }
24             //方式五
25             {
26                 Action<object> action = Test;
27                 ThreadPool.QueueUserWorkItem(n => Test("Action"));
28             }
29             //方式六
30             ThreadPool.QueueUserWorkItem((o) =>
31             {
32                 var msg = "lambda";
33                 Console.WriteLine("执行方法:{0}", msg);
34             });
35             
36             ......
37 
38             Console.ReadKey();
39         }
40         static void Test(object o)
41         {
42             Console.WriteLine("执行方法:{0}", o);
43         }
44         /*
45          * 作者:Jonins
46          * 出处:http://www.cnblogs.com/jonins/
47          */
48     }

推行结果如下:

图片 1

如上是使用线程池的三种写法,WaitCallback精神上是一个参数为Object类型无重临值的嘱托

1  public delegate void WaitCallback(object state);

所以符合要求的类型都可以如上述示范代码作为参数进行传递。

 

线程池常用艺术

ThreadPool常用的几个格局如下

方法 说明
QueueUserWorkItem 启动线程池里的一个线程(工作者线程)
GetMinThreads 检索线程池在新请求预测中能够按需创建的线程的最小数量。
GetMaxThreads 最多可用线程数,所有大于此数目的请求将保持排队状态,直到线程池线程由空闲。
GetAvailableThreads 剩余空闲线程数。
SetMaxThreads 设置线程池中的最大线程数(请求数超过此值则进入队列)。
SetMinThreads 设置线程池最少需要保留的线程数。

 示例代码:

 1         static void Main(string[] args)
 2         {
 3             //声明变量 (工作者线程计数  Io完成端口计数)
 4             int workerThreadsCount, completionPortThreadsCount;
 5             {
 6                 ThreadPool.GetMinThreads(out workerThreadsCount, out completionPortThreadsCount);
 7                 Console.WriteLine("最小工作线程数:{0},最小IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
 8             }
 9             {
10                 ThreadPool.GetMaxThreads(out workerThreadsCount, out completionPortThreadsCount);
11                 Console.WriteLine("最大工作线程数:{0},最大IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
12             }
13             ThreadPool.QueueUserWorkItem((o) => {
14                 Console.WriteLine("占用1个池化线程");
15             });
16             {
17                 ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
18                 Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
19             }
20             Console.ReadKey();
21         }

 执行的结果:

图片 2

注意:

1.线程有内存开支,所以线程池内的线程过多而从不完全采纳是对内存的一种浪费,所以要求对线程池限制最小线程数量。 

2.线程池最大线程数是线程池最多可创制线程数,实际意况是线程池内的线程数是按需创造。

 

I/O线程

I\O线程是.NET专为访问外部资源所引入的一种线程,访问外部资源时为了防范主线程长时间处于阻塞状态,.NET为多少个I/O操作建立了异步方法。例如:

FileStream:BeginRead、 style=”color: #0000ff;”>BeginWrite。调用BeginRead/BeginWrite时会发起一个异步操作,不过唯有在成立FileStream时传播FileOptions.Asynchronous参数才能取得真正的IOCP扶助,否则BeginXXX方法将会动用默许定义在Stream基类上的贯彻。Stream基类中BeginXXX方法会使用委托的BeginInvoke方法来倡导异步调用——那会利用一个相当的线程来执行职责(并不受IOCP协理,可能额外扩充属性损耗)。

DNS: style=”color: #0000ff;”>BeginGetHostByName、 style=”color: #0000ff;”>BeginResolve。

Socket:BeginAccept、 style=”color: #0000ff;”>BeginConnect、 style=”color: #0000ff;”>BeginReceive等等。

WebRequest: style=”color: #0000ff;”>BeginGetRequestStream、 style=”color: #0000ff;”>BeginGetResponse。

SqlCommand: style=”color: #0000ff;”>BeginExecuteReader、 style=”color: #0000ff;”>BeginExecuteNonQuery等等。那或者是开发一个Web应用时最常用的异步操作了。假若急需在实施数据库操作时取得IOCP协助,那么须要在连年字符串中标记Asynchronous
Processing为true(默许为false),否则在调用BeginXXX操作时就会抛出卓殊。

WebServcie:例如.NET 2.0或WCF生成的Web Service
Proxy中的BeginXXX方法、WCF中ClientBase<TChannel>的InvokeAsync方法。

这个异步方法的选拔方法都相比较相近,都是以Beginxxx初叶(内部贯彻为ThreadPool.BindHandle),以Endxxx结束。

注意

1.对此APM而言必须使用Endxxx截止异步,否则恐怕会造成资源败露。

2.委托的BeginInvoke方法并不可以取得IOCP援救。

3.IOCP不占用线程。

上面是行使WebRequest的一个示范调用异步API占用I/O线程:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int workerThreadsCount, completionPortThreadsCount;
 6             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
 7             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
 8             //调用WebRequest类的异步API占用IO线程
 9             {
10                 WebRequest webRequest = HttpWebRequest.Create("http://www.cnblogs.com/jonins");
11                 webRequest.BeginGetResponse(result =>
12                 {
13                     Thread.Sleep(2000);
14                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":执行最终响应的回调");
15                     WebResponse webResponse = webRequest.EndGetResponse(result);
16                 }, null);
17             }
18             Thread.Sleep(1000);
19             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
20             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
21             Console.ReadKey();
22         }
23     }

推行结果如下:

图片 3

有关I/O线程的情节点到此截至,感觉愈来愈多是I/O操作、文件等地点的知识点跟线程池瓜葛不多,想询问更加多戳:这里

 

执行上下文

种种线程都提到了一个执行上下文数据结构,执行上下文(execution
context)包罗:

1.安全设置(压缩栈、Thread的Principal属性、winodws身份)。

2.宿主设置(System.Threading.HostExecutionContextManager)。

3.逻辑调用上下文数据(System.Runtime.Remoting.Messaging.CallContext的LogicalGetData和LogicalSetData方法)。

线程执行它的代码时,一些操作会受到线程执行上下文限制,越发是安全设置的熏陶。

当主线程使用协理线程执行任务时,前者的履行上下文“流向”(复制到)辅助线程,那确保了扶助线程执行的其它操作使用的是如出一辙的平安设置和宿主设置。

默许景况下,CLR自动造成初步化线程的执行上下文“流向”任何赞助线程。但那会对质量造成影响。执行上下包括的大方新闻收集并复制到协理线程要消耗时间,倘使扶助线程又拔取了更加多的拉扯线程还必须创立和初始化更加多的推行上下文数据结构。

System.Threading命名空间的ExecutionContext类,它同意控制线程执行上下文的流动:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //将一些数据放到主函数线程的逻辑调用上下文中
 6             CallContext.LogicalSetData("Action", "Jonins");
 7             //初始化要由另一个线程做的一些事情,线程池线程能访问逻辑上下文数据
 8             ThreadPool.QueueUserWorkItem(state => Console.WriteLine("辅助线程A:" + Thread.CurrentThread.ManagedThreadId + ";Action={0}", CallContext.LogicalGetData("Action")));
 9             //现在阻止主线程执行上下文流动
10             ExecutionContext.SuppressFlow();
11             //初始化要由另一个线程做的一些事情,线程池线程能访问逻辑上下文数据
12             ThreadPool.QueueUserWorkItem(state => Console.WriteLine("辅助线程B:" + Thread.CurrentThread.ManagedThreadId + ";Action={0}", CallContext.LogicalGetData("Action")));
13             //恢复主线程的执行上下文流动,以避免使用更多的线程池线程
14             ExecutionContext.RestoreFlow();
15             Console.ReadKey();
16         }
17     }

结果如下:

图片 4

ExecutionContext类阻止上下文流动以升级程序的性质,对于服务器应用程序,品质的升官可能那么些明确。可是客户端应用程序的特性升高持续多少。别的,由于SuppressFlow方法用[SecurityCritical]特征标记,所以某些客户端如Silverlight中是力不从心调用的。

注意:

1.救助线程在不须求或者不访问上下文音讯时,应阻碍实施上下文的流淌。

2.推行上下文流动的连带知识,在运用Task目标以及提倡异步I/O操作时,同样有用。

 

三种异步方式(扫盲)&BackgroundWorker 

1.APM&EAP&TAP

.NET襄助两种异步编程形式分别为APM、EAP和TAP:

1.依照事件的异步编程设计情势 (EAP,Event-based Asynchronous
Pattern)

EAP的编程格局的代码命名有以下特征: 

1.有一个或八个名为 “[XXX]Async”
的措施。那些艺术恐怕会创制同步版本的镜像,这一个共同版本会在时下线程上推行同一的操作。
2.此类还可能有一个 “[XXX]Completed” 事件,监听异步方法的结果。
3.它恐怕会有一个 “[XXX]AsyncCancel”(或只是
CancelAsync)方法,用于废除正在进行的异步操作。

2.异步编程模型(APM,Asynchronous Programming Model)

APM的编程格局的代码命名有以下特征:

1.选择 IAsyncResult 设计情势的异步操作是通过名为[BeginXXX] 和
[EndXXX] 的八个办法来兑现的,那七个办法分别开始和完工异步操作
操作名称。例如,FileStream 类提供 BeginRead 和 EndRead
方法来从文件异步读取字节。

2.在调用 [BeginXXX]
后,应用程序可以持续在调用线程上推行命令,同时异步操作在另一个线程上执行。
每一趟调用 [BeginXXX] 时,应用程序还应调用 [EndXXX]
来获取操作的结果。

3.按照职务的编程模型(TAP,Task-based Asynchronous Pattern)

根据 System.Threading.Tasks 命名空间的 Task 和
Task<TResult>,用于表示任意异步操作。
TAP之后再议论。关于三种异步操作详细表明请戳:这里 

2.BackgroundWorker 

BackgroundWorker本质上是使用线程池内劳力线程,然而这么些类已经多余了(了然即可)。在BackgroundWorkerDoWork质量追加自定义方法,通过RunWorkerAsync将自定义方法追加进池化线程内处理。

DoWork精神上是一个事件(event)。委托项目限制为无再次回到值且参数有多个分级为Object和DoWork伊夫ntArgs类型。

1 public event DoWorkEventHandler DoWork;
2 
3 public delegate void DoWorkEventHandler(object sender, DoWorkEventArgs e);

演示如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int workerThreadsCount, completionPortThreadsCount;
 6             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
 7             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
 8             {
 9                 BackgroundWorker backgroundWorker = new BackgroundWorker();
10                 backgroundWorker.DoWork += DoWork;
11                 backgroundWorker.RunWorkerAsync();
12             }
13             Thread.Sleep(1000);
14             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
15             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
16             Console.ReadKey();
17         }
18         private static void DoWork(object sender, DoWorkEventArgs e)
19         {
20             Thread.Sleep(2000);
21             Console.WriteLine("demo-ok");
22         }
23     }

中间占用线程内线程,结果如下:

图片 5

 

结语

程序员使用线程池更多的是使用线程池内的工作者线程进行逻辑编码。

争执于独立操作线程(Thread)线程池(ThreadPool)能够管教总计密集作业的临时过载不会唤起CPU超负荷(激活的线程数量多于CPU内核数量,系统必须按时间片执行线程调度)。

过火会潜移默化属性,因为划分时间片须要大批量的上下文切换费用,并且使CPU缓存失效,而那一个是总结机落成飞速的要求调度。

CLR可以将任务进展排序,并且决定职分启动数量,从而幸免线程池超负荷。CLR首先运行与硬件基础数量同样多的面世义务,然后通过爬山算法调整并发多少,保险程序符合最优质量曲线。

 

参考文献

CLR via C#(第4版) Jeffrey Richter

C#高级编程(第10版) C# 6 & .NET Core 1.0   Christian Nagel  

果壳中的C# C#5.0高不可攀指南  约瑟夫 Albahari

http://www.cnblogs.com/dctit/

http://www.cnblogs.com/kissdodog/         

http://www.cnblogs.com/JeffreyZhao/

相关文章