说到.NET Threadpool我想你们都知道,只是平时比较零散,顾如今整理一下:html
一码阻塞,万码等待:ASP.NET Core 同步方法调用异步方法“死锁”的真相算法
.NET Threadpool starvation, and how queuing makes it worse缓存
New and Improved CLR 4 Thread Pool Engine并发
因此本文主要是验证和这里这几个文章app
当您调用ThreadPool.QueueUserWorkItem时,就是想象一个全局队列,其中工做项(本质上是委托)在全局队列中排队,多个线程在一个队列中选择它们。先进先出顺序。异步
左侧的图像显示了主程序线程,由于它建立了一个工做项; 第二个图像显示代码排队3个工做项后的全局线程池队列; 第三个图像显示了来自线程池的2个线程,它们抓取了2个工做项并执行它们。若是在这些工做项的上下文中(即来自委托中的执行代码),为CLR线程池建立了更多的工做项,它们最终会出如今全局队列中(参见右图),而且生命仍在继续。async
在CLR 4中,线程池引擎已对其进行了一些改进(它在CLR的每一个版本中都进行了积极的调整),而且这些改进中的一部分是在使用新的System.Threading.Tasks时能够实现的一些性能提高。建立和启动一个Task(传递一个委托),至关于在ThreadPool上调用QueueUserWorkItem。经过基于任务的API使用时可视化CLR线程池的一种方法是,除了单个全局队列以外,线程池中的每一个线程都有本身的本地队列oop
正如一般的线程池使用同样,主程序线程能够建立将在全局队列(例如Task1和Task2)上排队的任务,而且线程将一般以FIFO方式获取这些任务。事情分歧的是,在执行任务的上下文中建立的任何新任务(例如,Task2,Task23)最终在该线程池线程的本地队列上。性能
所以,从图片中进一步提高,让咱们假设Task2还建立了另外两个任务,例如Task4和Task5。ui
任务按预期结束在本地队列上,可是线程选择在完成当前任务(即Task2)时执行哪一个任务?最初使人惊讶的答案是它多是Task5,它是排队的最后一个 - 换句话说,LIFO算法能够用于本地队列。在大多数状况下,队列中最后建立的任务所需的数据在缓存中仍然很热,所以将其拉下并执行它是有意义的。显然,这意味着顺序没有承诺,但为了更好的表现,放弃了某种程度的公平。
其余工做线程完成Task1而后转到其本地队列并发现它为空; 而后它进入全局队列并发现它为空。咱们不但愿它闲置在那里,因此发生了一件美妙的事情:偷工做。该线程进入另外一个线程的本地队列并“窃取”一个任务并执行它!这样咱们就能够保持全部核心的繁忙,这有助于实现细粒度并行负载平衡目标。在上图中,注意“窃取”以FIFO方式发生,这也是出于地方缘由的好处(其数据在缓存中会很冷)。此外,在许多分而治之的场景中,以前生成的任务可能会产生更多的工做(例如Task6),这些工做如今最终会在另外一个线程的队列中结束,从而减小频繁的窃取。
线程池有 n+1 个队列,每一个线程有本身的本地队列(n),整个线程池有一个全局队列(1)。每一个线程接活(从队列中取出任务执行)的顺序是这样的:先从本身的本地队列中找活 -> 若是本地队列为空,则从全局队列中找活 -> 若是全局队列为空,则从其余线程的本地队列中抢活。
先看如下4个demo
1:若是你运行程序,你会发现它在控制台中设法显示“Ended”几回,而后就没有任何事情发生了,就像假死了
2:删除Task.Yield并在Producer中手动启动新任务。应用程序最初有点挣扎,直到线程池足够增加。而后咱们有一个稳定的消息流,而且线程数是稳定的
3:工做代码但在其本身的线程中启动Producer会怎么样,运行效果和1类似,有假死的效果, 可是感受比1 好点
4.Producer放回线程池线程,但在启动Process任务时使用PreferFairness标志,再次遇到第一种状况:应用程序锁定,线程数无限增长。
线程挑选项目排队的规则很简单:
该项将被排入全局队列:
若是排队项目的线程不是线程池线程
若是它使用ThreadPool.QueueUserWorkItem / ThreadPool.UnsafeQueueUserWorkItem
若是它使用Task.Factory.StartNew和TaskCreationOptions.PreferFairness标志
若是它在默认任务调度程序上使用Task.Yield
在几乎全部其余状况下,该项将被排入线程的本地队列
每当线程池线程空闲时,它将开始查看其本地队列,并以LIFO顺序对项目进行出列。若是本地队列为空,则线程将查看全局队列并以FIFO顺序出列。若是全局队列也为空,则线程将查看其余线程的本地队列并以FIFO顺序出列(以减小与队列全部者的争用,该队列以LIFO顺序出列)。
在代码的全部变体中,Thread.Sleep(1000)在本地队列中排队,由于Process老是在线程池线程中执行。但在某些状况下,咱们将Process排入全局队列,而将其余队列放入本地队列:
在代码的第一个版本中,咱们使用Task.Yield,它排队到全局队列
在第二个版本中,咱们使用Task.Factory.StartNew,它排队到本地队列
在第三个版本中,咱们将Producer线程更改成不使用线程池,所以Task.Factory.StartNew排队到全局队列
在第四个版本中,Producer再次是一个线程池线程,但咱们在将Process 排入队列时使用TaskCreationOptions.PreferFairness,所以再次使用全局队列
因为使用全局队列引发的优先级,咱们添加的线程越多,咱们对系统施加的压力就越大,当使用本地队列(代码的第二个版本)时,新生成的线程将从其余线程的本地队列中选择项目,由于全局队列为空。所以,新线程有助于减轻系统压力。