为什么使用线程池?
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源,所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因。比如大家所熟悉的数据库连接池正是遵循这一思想而产生的,本文将介绍的线程池技术同样符合这一思想。
多线程是什么?组成?特点?
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。
特点:
- 一个进程有且只有一个线程池。
- 线程池线程都是后台线程(即不会阻止进程的停止)
- 每个线程都使用默认堆栈大小,以默认的优先级运行,并处于多线程单元中。超过最大值的其他线程需要排队,但它们要等到其他线程完成后才启动。
- 在CLR 2.0 SP1之前的版本中,线程池中 默认最大的线程数量 = 处理器数 * 25, CLR 2.0 SP1之后就变成了 默认最大线程数量 = 处理器数 * 250,线程上限可以改变,通过使用ThreadPool.GetMax+Threads和ThreadPool.SetMaxThreads方法,可以获取和设置线程池的最大线程数。
- 默认情况下,每个处理器维持一个空闲线程,即默认最小线程数 = 处理器数。
- 当进程启动时,线程池并不会自动创建。当第一次将回调方法排入队列(比如调用ThreadPool.QueueUserWorkItem方法)时才会创建线程池。
- 在对一个工作项进行排队之后将无法取消它。
- 线程池中线程在完成任务后并不会自动销毁,它会以挂起的状态返回线程池,如果应用程序再次向线程池发出请求,那么这个挂起的线程将激活并执行任务,而不会创建新线程,这将节约了很多开销。只有线程达到最大线程数量,系统才会以一定的算法销毁回收线程。
什么时候使用线程池?
线程池常用方法介绍?
1、GetMaxThreads() : 获取可以同时处于活动状态的线程池请求的最大数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。
-
- 函数原型:public static void GetMaxThreads (out int workerThreads,out int completionPortThreads)
参数1:workerThreads :线程池中辅助线程的最大数目。
参数2:completionPortThreads :线程池中异步 I/O 线程的最大数目。
2、GetMinThreads() 获取线程池维护的最小空闲线程数。
-
- 函数原型:public static void GetMinThreads (out int workerThreads,out int completionPortThreads)
参数1:workerThreads:当前由线程池维护的空闲辅助线程的最小数目。
参数2:completionPortThreads:当前由线程池维护的空闲异步 I/O 线程的最小数目。
- 函数原型:public static void GetMinThreads (out int workerThreads,out int completionPortThreads)
3、SetMaxThreads() 设置可以同时处于活动状态的线程池的最大请求数目(不考虑计算机处理器的数目)
-
- 函数原型:public static bool SetMinThreads (int workerThreads,intcompletionPortThreads)
参数1:workerThreads::要由线程池维护的新的最小空闲辅助线程数。
参数2:completionPortThreads::要由线程池维护的新的最小空闲异步 I/O 线程数。
返回值:如果更改成功,则为 true;否则为 false。
4、SetMinThreads() 设置线程池在新请求预测中维护的空闲线程数(不考虑计算机处理器的数目)
-
- 函数原型:public static bool SetMinThreads (int workerThreads,intcompletionPortThreads)
参数1:workerThreads:要由线程池维护的新的最小空闲辅助线程数。
参数2:completionPortThreads:要由线程池维护的新的最小空闲异步 I/O 线程数。
返回值:如果更改成功,则为 true;否则为 false。
5、GetAvailableThreads() 获取由 GetMaxThreads 返回的线程池线程的最大数目和当前活动数目之间的差值。
-
- 函数原型:public static void GetAvailableThreads (out int workerThreads,out int completionPortThreads)
参数1:workerThreads:可用辅助线程的数目。
参数2:completionPortThreads:可用异步 I/O 线程的数目。
6、QueueUserWorkItem() 将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。
-
- 重载方法1:public static bool QueueUserWorkItem (WaitCallback callBack)
返回值:如果将方法成功排入队列,则为 true;否则为 false。
-
- 重载方法2:public static bool QueueUserWorkItem (WaitCallback callBack,Object state)
参数2:state :包含方法所用数据的对象。
返回值:如果将方法成功排入队列,则为 true;否则为 false。
备注:WaitCallback 回调方法必须与System.Threading.WaitCallback委托类型相匹配。
WaitCallback函数原型:public delegate void WaitCallback(Object state);调用QueueUserWorkItem可以通过Object来向任务过程传递参数。如果任务过程需要多个参数,可以定义包含这些数据的类,并将类的实例强制转换为Object数据类型。
7、UnsafeQueueUserWorkItem() 非安全性注册一个等待 WaitHandle 的委托(将方法排入队列以便执行)。
-
- 函数原型:public static bool UnsafeQueueUserWorkItem (WaitCallback callBack,Object state) //不将调用堆栈传播到辅助线程上。这允许代码失去调用堆栈,从而提升了它的安全特权。
- 备注:使用 UnsafeQueueUserWorkItem 可能会无意中打开一个安全漏洞。代码访问安全性的权限检查基于所有调用方对堆栈的权限进行。如果使用 UnsafeQueueUserWorkItem 将工作排在某个线程池线程上,则该线程池线程的堆栈将不会具有实际调用方的背景。恶意代码可能会利用这一点避开权限检查。
8、RegisterWaitForSingleObject() 将指定的委托排队到线程池。当发生以下情况之一时,辅助线程将执行委托。
9、UnsafeRegisterWaitForSingleObject() 非安全性将指定的委托排队到线程池。
线程池示例?
我们先来看一个简单的线程实例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Console.WriteLine("Begin in Main"); Thread t = new Thread(ThreadInvoke); t.IsBackground = true; t.Start(); //将当前线程挂起200毫秒 Thread.Sleep(200); Console.WriteLine("End in Main"); Console.ReadKey(); } static void ThreadInvoke(object obj) { for (int i = 0; i < 5; i++) { Console.WriteLine("Execute in ThreadInvoke"); //每隔100毫秒,循环一次 Thread.Sleep(100); } } } }
"End in Main"并没有在ThreadInvoke()方法中所有代码执行完之后才输出。
由此可见Main方法和ThreadInvoke是并行执行的
使用线程池改造上边的示例:
上面介绍了只是一个最简单的有关线程线程的例子,但在实际开发中使用的线程往往是大量的和更为复杂的,这时,每次都创建线程、启动线程。从性能上来讲,这样做并不理想(因为每使用一个线程就要创建一个,需要占用系统开销);从操作上来讲,每次都要启动,比较麻烦。为此引入的线程池的概念。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Console.WriteLine("Begin in Main"); ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadInvoke)); Console.WriteLine("End in Main"); //将当前线程挂起200毫秒 Thread.Sleep(3000); Console.ReadKey(); } static void ThreadInvoke(object obj) { for (int i = 0; i < 5; i++) { Console.WriteLine("Execute in ThreadInvoke"); //每隔100毫秒,循环一次 Thread.Sleep(100); } } } }
Thread.Sleep(3000)这句话是必须的因为当Main方法结束后,.Net环境会自动结束销毁线程池,为了保证完成线程池里的任务,所以主线程需要等待一段时间。
由输出结果可知,Main方法和ThreadInvoke方法是并行执行的。