CLR via C# 笔记 ----线程基础

CLR via C# 笔记 ----线程基础

在Windows中,进程是对应用程序需要的资源(内存)的抽象,就好像程序正在独占着这些资源(虚拟内存)一样

线程是对运行应用程序的cpu的抽象,就好像这个程序正在独占着这个cpu一样(然而此线程 会被其他线程抢占)

在Windows中,进程是十分昂贵的,创建一个进程要花几秒钟的时间,必须分配大量内存且初始化,EXE和DLL文件

必须从磁盘上加载,等等。相反,在Windows中创建线程则相对廉价。所以程序员倾向于多线程而不是多进程。

虽然线程相对廉价,但是他相对于其他系统资源来说却仍然十分昂贵,所以,还是应该省着用,用的恰当。

基于此,CLR提供了线程池的功能。一个进程有一个CLR(极大多数情况), 一个CLR有一个线程池,这个线程池被进程中所有的AppDomain(在一个进程里隔离多个应用程序内存空间的机制)共享。一开始线程池中没有任何线程,直到给他的工作项队列指派 了一个工作项:

//这是静态类ThreadPool中的一个方法,用来给线程池添加一个工作项 WaitCallback 等价于Action<object>
public static bool QueueUserWorkItem(WaitCallback callBack, object state);

此时,线程池会拿取这个工作项并创建一个线程,工作完成后,会把线程归还线程池,下次可以继续使用。

线程(虚拟地独占cpu)和其他所有的虚拟机制一样,必然有空间和时间上的额外消耗

空间上,线程创建时系统需要给他创建一个线程内核对象(一个数据结构),用来存放 一组对线程自我描述的一些属性,以及进程上下文(当线程被切换时,会将cpu所有寄存器的内容存放此处)。需要给他创建一个TEB(线程环境块,大约4Kb,这个结构保存着异常处理链,每当程序进入一个try块时会添加一个节点,退出时删除节点,这个结构还包含了”线程本地数据“,以及图形接口需要用到的一些数据结构)

需要给他创建用户模式栈(存放临时变量,参数返回值和跳转地址,大约1M的空间),内核模式栈(大约12K,提供安全的和内核交互的保障,机制类似于提交给内核的数据,先复制到这里,并且无法修改)

时间上,当线程创建时,会给进程中所有非托管DLL发出一个通知,例如C-Runtime就需要这个通知来为线程初始化一些东西。

(只有非托管的DLL才会收到通知,他会影响性能,但是这个通知可以选择不发送)

最后,线程的最大消耗来自于所谓的上下文切换

当操作系统决定要切换掉cpu当前的这个线程时,会先将此时cpu的寄存器数据保存到线程的上下文结构中,然后将另一个线程的上下文数据恢复到cpu的寄存器,如果这个线程是另一个进程的,还需要切换cpu看见的虚拟地址空间

更进一步的,当线程切换时,意味着存放在高速缓存中的数据失去了意义,吃掉了专门为了缓存命中而进行优化的努力

笔记总结,多线程可以显著增加程序的性能,但是线程是昂贵的资源,使用不当,也非常容易负优化。