【C# 线程】线程池 ThreadPool


Overview

   如今的应用程序越来越复杂,我们常常需要使用《异步编程:线程概述及使用》中提到的多线程技术来提高应用程序的响应速度。这时我们频繁的创建和销毁线程来让应用程序快速响应操作,这频繁的创建和销毁无疑会降低应用程序性能,我们可以引入缓存机制解决这个问题,此缓存机制需要解决如:缓存的大小问题、排队执行任务、调度空闲线程、按需创建新线程及销毁多余空闲线程……如今微软已经为我们提供了现成的缓存机制:线程池

1、.NET框架为每一个进程提供了一个线程池,每当您启动线程时,都会花费几百微秒来组织诸如新的私有局部变量堆栈之类的东西。
2、只有全局一个队列和n本地线程任务队列,无法取消任务,无法限制任务执行速度等等
3、当一个等待操作完成时,线程池中的一个辅助线程就会执行对应的回调函数
4、线程池中的线程由系统进行管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。
5、线程池线程都是后台线程。每个线程都使用默认堆栈大小1MB,以默认的优先级运行,并处于多线程单元中,您可以随意更改池线程的优先级— 当释放回池时,它将恢复正常。线程池通过共享和回收线程来减少这些开销,从而允许在非常精细的级别应用多线程,而不会降低性能。
6、如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间之后创建另一个辅助线程。
7、但线程的数目永远不会超过最大值。超过最大值的其他线程可以排队,但它们要等到其他线程完成后才启动。
8、如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。
9、一种是用于处理CPU密集型逻辑的工作线程,即它主要使用CPU来执行其逻辑,如任何计算等,另一种是I / O线程,这些线程用于执行I / O请求或耗时的请求,例如读/写到文件系统, 调用数据库或调用任何第三方 API/请求。
10、线程池不保证全局队列工作项的处理顺序
11、如果工作项完成的时间太长(具体多长没有正式公布)线程池会创建更多的工作者线程,如果工作像完成速度开始变快,工作者线程会被销毁
线程池从其池中的一个线程开始。分配任务时,池管理器会"注入"新线程以处理额外的并发工作负载,最高可达最大限制。在足够长的不活动时间后,如果池管理器怀疑这样做会带来更好的吞吐量,则池管理器可能会"停用"线程。
您可以通过调用 来设置池将创建的线程的上限;默认值为:ThreadPool.SetMaxThreads
12、如果全局队列也为空,工作者线程会进入睡眠状态等待事情的发生,如果睡眠了太长时间,他会自己醒来并销毁至深允许系统回收线程使用的资源
13、最好是将线程池看成一个黑盒,不要拿单个应用程序去衡量它的性能,因为它不是针对某个单独应用程序而设计的。线程池内部会更改它的管理线程的方式,所以大多应用程序的性能会变的越来越好

 

1、线程池的工作原理

1、.net 会为每个进程生成一个线程池。线程池的初始值线程数是 cpu逻辑内核数。后面连续建线程要每间隔500ms新建一个线程。即使突然并发大量任务也是按这个进度进来线程。
2、可以通过设置min thread(默认等于cpu内核数) 提高 线程池一开始的线程数,从而提高并发数。 后面连续建线程要每间隔500ms新建一个线程。
3、本地队列:线程池中的每一个线程都会绑定一个 ThreadPoolWorkQueueThreadLocals 实例,在 workStealingQueue 这个字段上保存着本地队列。
4、全局队列:每个进程只有一个全局队列, 是由 ThreadPoolWorkQueue 维护的,同时它也是整个队列系统的入口,直接被 ThreadPool 所引用。
5、在线程池线程A中生成的任务,会安排入当前线程A的任务队列。
6、当前线程池的线程A的任务队列已经执行完成,如果全局任务队列也没有任务了,就会去查看其他线程B的任务队列,如果其他线程B的任务队列还有很对没完成的,当前线程A就会偷它B的任务执行。
7、线程池使用IO模型有两种:和。

8、线程池将自己的线程划分工作者线程(辅助线程)和I/O线程。前者用于执行普通的操作,后者专用于异步IO,比如文件和网络请求,注意,分类并不说明两种线程本身有差别,内部依然是一样的。

 

参考:https://www.cnblogs.com/eventhorizon/p/15316955.html#ithreadpoolworkitem-%E5%AE%9E%E7%8E%B0%E7%B1%BB%E7%9A%84%E5%AE%9E%E4%BE%8B

2  线程池的应用范围

在以下几种情况下,适合于使用线程池线程:

(1)不需要前台执行的线程。
(2)不需要在使用线程具有特定的优先级。
(3)线程的执行时间不易过长,否则会使线程阻塞。由于线程池具有最大线程数限制,因此大量阻塞的线程池线程可能会阻止任务启动。
(4)不需要将线程放入单线程单元。所有 ThreadPool 线程均不处于多线程单元中。
(5)不需要具有与线程关联的稳定标识,或使某一线程专用于某一任务。
(6)一种是在应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应,这一般使用ThreadPool(线程池)来解决
(7)一种情况是在线程平时都处于休眠状态,只是周期性地被唤醒,这一般使用Timer(定时器)来解决。下面对ThreadPool类进行详细说明。

3、有多种方法可以进入线程池:

  • 通过任务并行库(来自框架 4.0)
  • 通过呼叫ThreadPool.QueueUserWorkItem
  • 通过异步委托
  • 通过BackgroundWorker

4、以下构造间接使用线程池:

  • WCF、远程处理、ASP.NET 和 ASMX Web 服务应用程序服务器
  • System.Timers.Timer System.Threading.Timer
  • 异步结尾的框架方法,例如 on(基于事件的异步模式)和大多数XXX方法(异步编程模型模式)WebClientBegin
  • 普林克

5、优化线程池

线程池从其池中的一个线程开始。分配任务时,池管理器会"注入"新线程以处理额外的并发工作负载,最高可达最大限制。在足够长的不活动时间后,如果池管理器怀疑这样做会带来更好的吞吐量,则池管理器可能会"停用"线程。
可以通过调用 来设置池将创建的线程的上限;默认值为:workerThreads:  默认大小逻辑cpucore数, completionPortThreads:  默认大小逻辑。
 max threads 初始值:32位平台 1023,64位平台 short.MaxValue32767。

32位环境中的  workerThreads:1023  默认大小逻辑cpucore数, completionPortThreads:25   默认大小逻辑cpucore数
64位环境中的  workerThreads:32767 默认大小逻辑cpucore数, completionPortThreads:1000  默认大小逻辑cpucore数
默认数字是由进程的关联掩(affinity mask)码决定的。

min threads 初始值:运行环境 CPU 核心数,可通过 ThreadPool.SetMinThreads 进行设置,参数有效范围是 [1, max threads]。(这些数字可能因硬件和操作系统而异。之所以存在许多线程,是为了确保某些线程被阻止时取得进展(在等待某些条件时空转,例如来自远程计算机的响应)。
您还可以通过调用 来设置下限。下限的作用更微妙:它是一种高级优化技术,指示池管理器在达到下限之前不要延迟线程的分配。当存在阻塞的线程时,提高最小线程计数可提高并发性(请参阅侧边栏)。ThreadPool.SetMinThreads

CompletionPort的知识点:

6、排队工作项

1、通过调用 ThreadPool.QueueUserWorkItem 并传递 WaitCallback 委托来使用线程池。
2、通过使用 ThreadPool.RegisterWaitForSingleObject 并传递 WaitHandle(在向其发出信号或超时时,它将引发对由 WaitOrTimerCallback 委托包装的方法的调用)来将与等待操作相关的工作项排队到线程池中。
若要取消等待操作(即不再执行WaitOrTimerCallback委托),可调用RegisterWaitForSingleObject()方法返回的RegisteredWaitHandle的 Unregister 方法。
3、如果您知道调用方的堆栈与在排队任务执行期间执行的所有安全检查不相关,则还可以使用不安全的方法 ThreadPool.UnsafeQueueUserWorkItem 和 ThreadPool.UnsafeRegisterWaitForSingleObject。UnsafeQueueUserWorkItem 和 RegisterWaitForSingleObject都会捕获调用方的堆栈,此堆栈将在线程池线程开始执行任务时合并到线程池线程的堆栈中。如果需要进行安全检查,则必须检查整个堆栈,但它还具有一定的性能开销。使用“不安全的”方法调用并不会提供绝对的安全,但它会提供更好的性能。

7、方法的是使用

 由于饥饿和死锁的问题存在,所以不建议使用GetMaxThreads、SetMaxThreads、GetMinThreads、SetMinThreads、GetAvailableThreads--CLR Via C#

ThreadPool.QueueUserWorkItem 用法
int WorkThreadCount, CompletionThreadcCont;

ThreadPool.GetMaxThreads(out WorkThreadCount, out CompletionThreadcCont);

ThreadPool.SetMinThreads(1001, 10);
ThreadPool.GetMinThreads(out WorkThreadCount, out CompletionThreadcCont);// 默认最小值是cpu个数
ThreadPool.QueueUserWorkItem(new WaitCallback(count), null);

Console.WriteLine(ThreadPool.PendingWorkItemCount);//当前排队等候处理的工作项的数目。
Console.WriteLine(ThreadPool.CompletedWorkItemCount);//到目前为止已处理的工作项的数目。
Console.WriteLine(ThreadPool.ThreadCount);//获取当前程序的 线程池的线程数
ThreadPool.GetAvailableThreads(out WorkThreadCount, out CompletionThreadcCont);//获取剩余的可用线程
count(null);
 
void count(object? obg)
{

    Console.WriteLine($"WorkThreadCount:{WorkThreadCount},CompeTionThreadcCont:{CompletionThreadcCont}");

}
//将方法委托给线程池
ThreadPool.QueueUserWorkItem(  o => {
    int i;


    i = Convert.ToInt32(o);
    i++;
    Console.WriteLine("sfdsdf");
},1 );
C