1. 线程基础
Windows中,线程的职责是对CPU进行虚拟化,可将线程理解为一个逻辑CPU。Windows为每个进程提供该进程专用的线程。
由于线程是对CPU进行虚拟化,使得线程会产生空间(内存耗用)和时间(上下文切换)上的开销。
1. 空间开销:在默认情况下,Windows为每个线程的用户模式栈分配1MB内存。
2. 时间开销:在任何给定的时刻,Windows只将一个线程分配给一个CPU。该线程允许运行一个“时间片”。一旦时间片到期,Windows就上下文切换到另一个线程。Windows执行一次上下文切换大约需要30毫秒。
结论:尽可能避免使用线程,但有时必须使用线程来提高程序的响应速度和执行多任务。
2. 前后台线程
在CLR中的线程只有前台线程和后台线程两种。一个进程中所有前台线程都停止运行时,CLR将强制终止所有正在运行的后台线程,并且不会抛出异常。只要有一个前台线程在运行,应用程序的进程就在运行。所以前台线程适合执行关键任务,后台现在适合执行非关键任务。下面代码用于说明前后台线程与进程的关系。
private static void Main(string[] args) { //创建专用线程 var t = new Thread(ThreadWork) { Name = "NewThread1", //指定是否为后台线程,修改该属性验证前后台线程与进程的关系 IsBackground = false }; //创建专用线程 t.Start(); Console.WriteLine("进程结束"); } private static void ThreadWork() { Console.WriteLine("线程启动:" + Thread.CurrentThread.Name); //让当前线程休眠3秒钟,把线程执行权让给优先级高的其他线程执行, //避免一直占有线程执行权。3秒钟过后线程醒来,一定能立即恢复执行。 //这是因为在那个时刻,其它线程可能正在运行而且没有被调度为放弃执行,除非 //1、“醒来”的线程具有更高的优先级 //2、正在运行的线程因为其它原因而阻塞。 Thread.Sleep(3000); Console.WriteLine("线程完成" + Thread.CurrentThread.Name); //这个方法返回后,该专用线程将终止。 }
3. 线程池
3.1. 线程池基础
由第一节线程基础的结论可知,我们应该在保持代码响应能力的同时创建尽可能少的线程。CLR线程使用Windows的线程处理能力,一个CLR线程直接对应一个Windows线程。为了改善创建和销毁线程带来的空间和时间上的开销,CLR包含代码来管理它自己的线程池。可将线程池想象为可由你的应用程序使用的一个线程集合。每个CLR一个线程池。
CLR初始化时,线程池中没有线程。在内部,线程池维护一个操作请求队列。应用程序向线程池发出一个请求时,如果线程池中没有线程,就创建一个新线程用于处理应用程序的请求。当线程池中的线程完成任务后,线程不会被销毁,而是返回到线程池中,并且进入空闲状态,等待响应另一个请求。由于线程池中的线程处理完任务后不销毁自身,所以不再产生额外的性能损失。如果应用程序停止向线程池发出请求,线程池中的线程都处于空闲状态,一段时间后,线程会自己醒来终止自己并且释放资源。线程终止自己时会产生一定的性能损失,然而,线程池中的线程终止自己是因为闲得慌,说明应用程序本身没做什么事,所以这个性能损失关系不大。
3.2. 线程池管理
不同CLR的版本,其内部结构有一定的变化。最好将线程池看成黑盒子。目前,线程池的工作非常理想,强烈建议信任它。
1. 设置线程池上限
CLR允许开发人员设置线程池的最大线程数,但永远都不应该为线程池设置上限,因为可能发生饥饿或死锁。由于存在饥饿和死锁问题,所以CLR团队一直都在稳步增加线程池默认能够拥有的最大线程数,目前默认值为1000个线程。
注:参考CLR via C#第三版 第25、26章