前言
在校时认识的线程就是获取CPU执行时间的最小单位,多个线程共享所在进程的资源和内存空间,偶然会听说线程拥有上下文这一概念,但没有深入了 解学习,如今工作一年多后顿悟要及时补回这方面的知识于是参考各大哥们所分享的资料,学习、总结一下自己对线程的理解,本篇内容主要从原理、使用上记录讲 解线程相关知识,其中若有谬误请各位多多指正,并该篇会随自身对线程的理解不断的修改扩充,多谢关注。
主要参考:.net 4.0 学习笔记(3)—— 线程基础(上)
初识线程
参考:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=231910
线程是进程内部的“轻量级”执行单位(比进程便宜但比分配其他资源昂贵),多个线程共享进程内的代码段和数据段,每个线程拥有独立的程序计数器、寄存器和堆栈,共享全局变量和静态变量。
现阶段.net中CLR的线程是一一映射到Windows操作系统的物理线程(Jeffrey Richter预言.net迟早会实现CLR的逻辑线程多对一映射到Windows操作系统的物理/本地线程,降低多线程的开销)。所以下面让我们从 Windows操作系统的线程开始说起吧
Windows线程
1.线程含有的元素:
a.线程内核对象(Thread Kernel):操作系统分配给线程的数据结构,该数据结构用于描述线程的属性,其中包含线程上下文。
线程上下文包括:安全上下文、同步上下文(System.Threading.SynchronizationContext)、本地化上下 文、调用上下文(System.Threading.ExecutionContext)、事务上下文和CLR宿主上下文 (System.Threading.HostExecutionContext)。
执行上下文:是某一个时刻的线程上下文,如进程是执行中的程序那样,执行中的线程上下文就是执行上下文。通过System.Threading.ExecutionContext获取,。
线程调度时上一个线程的CPU寄存器的状态会保存到该线程上下文中。如果两个线程属于同一个进程时,会根据是否压抑执行上下文流动来决定是否复制上一个线程的上下文到要执行的线程的上下文中(具体请留意上下文的流动);如果不属于同一个进程,就要在执行代码和访问数据前改变虚拟内存地址。然后加载要执行的线程的上下文到CPU寄存器并执行线程。
b.线程环境块 (Thread Environment Block):用于在用户模式(应用程序能快速访问的地址空间)分配和初始化一个内存块,消耗1个内存页(4KB在x86和x64 CPU上,8KB在IA64 CPU上)。其中还包含一个线程异常处理链头,当线程进入try语句块时就会将一个节点插入到该链头,退出try语句块时就从立案头移除该节点。
c.用户模式堆栈(User Mode Stack):用于保存方法的局部变量、参数和方法返回时继续执行的地址。Windows默认分配1MB给用户模式堆栈。
d.内核模式堆栈(Kernel Mode Stack):windows默认分配12KB(x86系统)或24KB(x64系统)。
主要有2个功能:一、当应用程序调用内核功能时,会将用户模式堆栈中的参数复制到内核模式堆栈,复制成功后内核会核实参数的值,而因为应用程序 不能访问内核模式堆栈,所以在参数在核实后无法被修改,从而保证内核功能被安全地调用;二、用于保存内核方法的局部变量、参数和方法返回后继续执行的地 址。
e.DLL线程加载和卸载通知:请参考.net 4.0 学习笔记(3)—— 线程基础(上),因托管DLL不存在该情况,这里暂不讨论^_^。
CLR线程、操作系统线程和CPU关系
下图是三者的关系(单核CPU)
图上的进程均是CLR的托管进程,其中的线程为CLR的托管线程。一个托管进程对应一个本地进程,一个托管线程对应一个本地线程。
1.杀死其他进程:可以通过System.Diagnostics.Process.GetProcessByID方法根据PID(进程ID)获取某一正在运行的进程,然后调用该进程的CloseMainWindow或Kill方法杀死该进程。但不能杀死自己启动的进程,原因有待研究。。。。希望大哥们讲解一下
CloseMainWindow方法并不是强行杀死进程,而是如用户点击程序的关闭按钮一样关闭进程,所以可以在程序关闭事件中作处理操作甚至阻止 关闭进程的操作;而Kill是强行杀死进程,程序没有机会执行任何善后工作;而Close方法如dispose方法只是释放资源而没有杀死进程。
杀死进程Code:
int PID = 123;
Process p = Process.GetProcessById(PID);
if(!p.CloseMainWindow())
{
p.Kill();
}
启动进程Code:
1 ProcessStartInfo psi = new ProcessStartInfo(@"C:\Program Files\KWMUSIC\KwMusic.exe");
2 Process d = Process.Start(psi);
上述操作均是通过本地线程执行。
2.GC执行时所有CLR线程均挂起,执行完后CLR线程会恢复继续等待调度。越多线程该过程越低效。
题外内容:
进程特点:
1.进程拥有独立的内存地址空间,一般情况包含:文本区域(text region),用于存储处理器执行的代码;数据区域(data region),用于存储变量和进程执行期间动态分配的内存;堆栈(stack),用于存储进程执行期间调用的指令和本地变量。
2.进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
前台线程与后台线程
前台、后台线程是CLR中的概念,CLR认为线程要不是前台线程就一定为后台线程。当进程中的所有前台线程都结束后,CLR会强制结束所有后台 线程,并且不会抛出异常,最后退出进程释放所有资源。线程池的线程默认为后台线程,而使用Thread生成的线程默认为前台线程,可以设置其实例 IsBackground属性设为后台线程。
线程优先级
线程的优先级从0(低)到31(高),但CLR不允许开发者直接设置这些优先级,而是提供7种优先级方便开发者操作。注意,线程的优先级并不是 单独起作用的,而是与所属进程的优先级关联起来后决定线程的真实优先级(进程拥有6种优先级)。进程优先级与线程优先级关联后线程优先级如下图:
Windows被称作抢先式多线程操作系统,其线程调度过程如下:
1.系统搜寻最高优先级的线程并将其分配到CPU执行;
2.当某个线程正被CPU执行时,有一个优先级更高的线程加入到队列中,那么就会挂起正在执行的线程而将优先级更高的线程分配到CPU。
创建线程
有两种方法:一、使用线程池(System.Threading.ThreadPool,推荐使用);二、使用Thread对象。
一、线程池:
使用ThreadPool.QueueUserWorkItem(WaitCallBack callBack)方法从线程池获取空闲线程执行代码,线程池中的线程的优先级均为Normal,默认为后台线程。
二、使用Thread对象,默认为前台线程:
1 Thread t = new Thread(ParameterizedThreadStart start);//构造逻辑线程
2 t.Start(object state);//生成物理线程(实际对应一个操作系统的线程)
当符合以下条件时就用Thread对象吧,否则还是用线程池较好。
1.你需要线程允许一个非普通优先级。所有的线程池线程都允许在普通优先级。当然,这你可以改变,但是不推荐,在线程池操作过程中,优先级的改变不会持续。
2.你需要线程作为前台线程运转,从而防止程序终止一直到线程完成任务。线程池线程总是后台线程,如果CLR决定终止进程它们就不会完成任务。
3.受计算限制的任务需要时间非常长;这样,我不会让线程池负担逻辑,因为它试图找出是否需要创建一个额外的线程。
4.我想开始线程并很可能用Thread.Abort方法来过早的结束它。
线程上下文流动发生在线程调度时,当前后线程属于同一进程并没有阻止上下文流动时,在下一个线程执行代码和访问数据前将前一个线程的上下文会复制到下一个线程的上下文中,然后执行下一个线程。作用:这里打个比方,如单点登录系统那样在一个网站登录后浏览其他系统中的其他网站由于登录验证信息已经共享所以不需要重新登录验证,而线程上下文的复制就如登录验证信息共享那样。
因上下文流动会降低一定的性能,所以在需要的情况下可以阻止上下文流动(如下一个线程执行不需要上一个线程的上下文信息)。下面提供两种方法实现阻止上下文流动:1,使用线程池System.Threading.ThreadPool.UnsafeQueueUserWorkItem方法调用另一个线程来执行代码;2,使用System.Threading.ExecutionContext.SuppressFlow方法来阻止当前线程将上下文复制到下一个线程的上下文。具体例子请参考:如何阻止线程执行上下文的传递