CLR 线程基础 node
一、什么是进程:算法
操做系统三个基本的抽象概念:windows
1 文件 : 对 I/O 设备的抽象表示;缓存
2 虚拟内存: 对主存 和 磁盘I/O 设备的抽象;安全
3 进程: 对处理器、主存 和 I/O 设备的抽象表示。网络
进程是操做系统对一个正在运行的程序的一种抽象。在一个系统上能够同时运行多个进程,而每一个进程都好像独占地使用硬件。并发的执行多个进程,是处理器在进程间切换实现的。操做系统实现这种交错执行的机制称为上下文切换。数据结构
所谓 上下文 ,就是进程运行时所须要的全部状态信息。好比:PC 和 寄存器文件的当前值,以及主存的内容。当一个进程中止,操做系统将当前进程的上下文保存起来,恢复另外一个进程的上下文,将控制权交给另外一个进程。另外一个进程从它上次中止的地方开始。并发
逻辑控制流:async
程序被编译成一条条指令和数据,这一系列的程序计数器(PC)的值的序列称为:逻辑控制流 每一个进程执行它的逻辑控制流的一部分,而后被抢占,而后轮到其余进程。函数
私有地址空间:
每一个进程都有本身的私有地址空间,它关联内存中一段内容。
用户模式和内核模式:
内核模式能够执行任意指令,访问任意内存地址。进程从用户模式切换到内核模式惟一的方法是经过诸如中断、故障、或者陷入系统调用这样的异常。
上下文切换的时机:
1 内核表明用户执行系统调用时;
2 sleep 系统调用;
3 中断也可引发上下文切换。中断:系统都有某种产生周期性定时器中断机制,1- 10 毫秒,每次发生中断时内核会判断当前进程运行的时间,是否须要切换到另外一个进程。
在从A 进程切换到B 进程以前,内核表明进程A 在用户模式下执行指令。在切换的第一部分,内核表明进程A 在内核模式下执行指令。而后在某一时刻,它开始表明进程B 执行指令(任然是在内核模式下)。在切换以后,内核表明进程B 在用户模式下执行指令。
二、线程的概念
进程是对对处理器、主存 和 I/O 设备的抽象表示,那线程就是对CPU计算资源的抽象表示。Windows 为每一个进程都提供了该进程专用的线程(功能至关于一个CPU)。进程中的主线程,是建立进程时为其分配的。进程要作任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。每一个线程都有本身的一组 CPU 寄存器和 它本身的堆栈。
线程开销
每一个线程都有如下要素:
线程内核对象(thread kernel object)
线程内核对象你能够把它想象成是一种数据结构,它保存在OS 为线程分配的内存中。这个数据结构包含:
1 描述线程的属性。
2 线程上下文(包含CPU 寄存器集合的内存块)。
x86 占700字节,x64占1240字节,ARM 占350 字节。
线程环境块(thread environment block,TEB)
TEB 是在用户模式(应用程序代码能快速访问的地址空间)中分配和初始化的内存块。TEB 耗用1 个内存页(x8六、x6四、ARM 都是4KB)。
TEB包含线程的异常处理链首(head)。线程进入的每一个try 块都在链首插入一个节点(node);线程退出try块时从链中删除该节点。
此外,TEB还包含线程的“线程本地存储”数据,以及由GDI(Graphics Device Interface,图形设备接口)和 OpenGL 图形使用的一些数据结构。
用户模式栈(user-mode stack)
用户模式栈存储传给方法的局部变量和实参。它还包含一个地址:指出当前方法返回时,线程应该从什么地方接着执行。
Windows 默认为每一个线程的用户模式栈分配1MB内存。更具体地说,windows 只是保留1 MB地址空间,在线程实际须要时才会提交物理内存。
内核模式(kernel-mode stack)
应用程序代码向操做系统中的内核模式函数传递实参时,还会使用内核模式栈。出于对安全的考虑,针对从用户模式的代码传给内核的任何实参,Windows 都会把它们从线程的用户模式栈复制到线程的内核模式栈。32 位windows 内核模式栈 12KB,64位windows是24 KB。
DLL 线程链接(attach)和线程分离(detach)通知
windows的一个策略是,任什么时候候在进程中建立线程,都会调用进程中加载的全部非托管DLL的 DllMain方法,并向该方法传递DLL_THREAD_ATTACH 标志。
相似地,任什么时候候线程终止,都会调用进程中的全部非托管DLL 的DllMain方法,并向方法传递DLL_THREAD_DETACH 标志。
有的DLL 须要获取这些通知,才能为进程中建立/销毁的每一个线程执行特殊的初始化或(资源)清理操做。
咱们目前使用的电脑,随便一个进程也得由几百个DLL,要是每次在应用程序中建立一个线程,都必须先调用几百个DLL 函数,而后线程才能开始作它想作的事。在线程终止时还须要将这几百个DLL 函数再调用一遍。这严重影响在进程中建立和销毁线程的性能。
CPU如何上下文切换线程
1 CPU要切换另外一个线程,首先要保存当前线程的状态,将要执行的线程数据缓存在高速cache中,高速运行30毫秒后再切换线程. windows大约每30毫秒执行一次上下文切换.
2 正在等待IO操做完成的线程,不会被CPU调度.因此不会浪费CPU时间,
3 一个时间片结束时,若是Windows再次调度同一个线程,那么Windows不会执行上下文切换,接着当前线程继续执行.
尽可能避免使用线程,它们消耗大量的内存,须要不少时间来建立,销毁,管理.windows在线程间上下文切换,以及在发生垃圾回收的时候,也会浪费很多时间.
若是只关心性能,任何机器最优的线程数就是它拥有的CPU核数.可是Microsoft设计windows时,追求的是可靠性,和响应能力.
对线程的使用尽可能从线程池中获取。在极少数状况可能须要显示建立线程来专门执行一个计算限制的操做。知足如下任何条件,就能够显示建立本身的线程:
线程须要以非普通线程优先级运行。全部线程池线程都以普通优先级运行;能够更改线程池的线程优先级,可是不建议那么作。
须要线程表现为一个前台线程,防止应用程序在线程结束任务前终止。线程池是始终是后台线程,若是CLR想终止进程,后台线程会跟着终止。
计算限制的任务须要长时间运行。线程池为了判断是否须要建立一个额外的线程,所采用的逻辑是比较复杂的。直接为长时间运行的任务建立专用线程,就能够避免这个问题。
要启动线程,并可能调用thread 的abort 方法来提早终止它。
要建立专用线程,要构造System.Threading.Thread 类的实例,想构造器传递一个方法名。
using System;
using System.Threading;
public static class Program {
public static void Main() {
Console.WriteLine("Main thread: starting a dedicated thread " +
"to do an asynchronous operation");
Thread dedicatedThread = new Thread(ComputeBoundOp);
dedicatedThread.Start(5);
Console.WriteLine("Main thread: Doing other work here...");
Thread.Sleep(10000); // Simulating other work (10 seconds)
dedicatedThread.Join(); // Wait for thread to terminate
Console.WriteLine("Hit <Enter> to end this program...");
Console.ReadLine();
}
// This method's signature must match the ParameterizedThreadStart delegate
private static void ComputeBoundOp(Object state) {
// This method is executed by a dedicated thread
Console.WriteLine("In ComputeBoundOp: state={0}", state);
Thread.Sleep(1000); // Simulates other work (1 second)
// When this method returns, the dedicated thread dies
}
}
编译并运行上述代码可能获得如下输出:
Main thread: starting a dedicated thread to do an asynchronous operation
Main thread: Doing other work here...
In ComputeBoundOp: state=5
但也可能获得如下输出:
Main thread: starting a dedicated thread to do an asynchronous operation
In ComputeBoundOp: state=5
Main thread: Doing other work here...
线程调度和优先级
抢占式操做系统必须使用算法判断在何时调度哪些线程多长时间。前面说过每一个线程的内核对象都包含一个上下文结构。上下文结构反映了线程上一次执行完毕后 CPU寄存器的状态。在一个时间片(time-slice)以后,widows检查现存的全部线程内核对象。在这些对象中,只有哪些没有正在等待什么的线程才适合调度。windows 选择一个可调度的线程内核对象,并上下文切换到它。windows还记录了每一个线程被上下文切换的次数。
windows 系统作不到很精确的控制线程在你想要的某一时刻执行想要执行的程序。例如:怎样保证一个线程在网络有数据传来的1毫秒内开始运行?这个windows办不到。实时操做系统能够作出这种保证。CLR 使托管代码的行为变得更不“实时”。
windows 支持6 个进程优先级类:
Idle、Below Normal、Normal、Above Normal、High 和 Realtime。默认的Normal 是最经常使用的优先级。
Realtime 优先级要尽量的避免使用,它的优先级至关高,能够干扰操做系统任务。
windows 支持7 个相对线程优先级:
Idle、Lowest、Below Normal、Normal、Above Normal、Highest 和 Time-Critical。
这些优先级是相对于进程优先级类而言的。一样 Normal 是默认优先级。
系统将进程优先级类和其中的一个线程相对优先级映射成一个优先级(0~31)。下表总结了它们的关系。
注意:
表中没有值为0 的优先级,由于0优先级保留给零页线程。系统不容许其余线程的优先级为0。
并且,如下优先级也不能够得到:17,18,19,20,21,27,28,29 或 30。之内核模式运行的设备驱动程序才能得到这些优先级。
Realtime 优先级类中的线程优先级不能低于16。
非Realtime 的优先级类中的线程优先级不能高于15。
托管应用程序不该该表现为拥有它们本身的进程;相反,它们应该表现为在一个AppDomain 中运行。因此,托管应用程序不该该更改它们的进程的优先级,由于这会影响进程中运行的全部代码。
线程是很是宝贵的资源,必须省着用。使用线程最好的方式就是从线程池中获取。由线程池来管理线程的建立和销毁,这是最可靠的。