进程和线程编程
进程是一个系统级别的概念,用来描述一组资源和程序运行所必须的内存分配。每个进程都有一个惟一的进程标识符(PID);线程是进程的基本单元;进程的入口点建立的第一个线程被称为主线程;线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。多线程
应用程序域(AppDomain):.Net可执行程序承载在进程中的逻辑分区;一个进程能够包含多个应用程序域,每个应用程序域中承载一个.Net可执行程序;每个程序域都和该进程(或其它进程)中的其它的程序域彻底完全隔离开,只有使用分布式编程协议(如WCF)才能访问其它应用程序域中的任何数据;架构
System.AppDomain并发
一个线程能够穿梭在多个应用程序域中,但在某个时刻,线程只会处于一个应用程序域内。 异步
上下文边界分布式
每一个线程都有本身的属性,在每一个线程的内核对象之中,都包含一个上下文结构,上下文结构的存在是为了反映在线程上一次执行时,线程CPU寄存器的状态。在任什么时候刻,Windows只将一个线程代码分配给一个CPU,一个线程容许运行一个时间片,在线程的“时间片”结束以后,Windows会检查现有全部线程内核对象,只有那些没有在等待什么的线程才适合调度。Windows选择一个可调度的线程内核对象,而且换到它。异步编程
Windows选择一个可调度的线程有一套独特的标准,Windows执行线程的规律和时间片没多大的关系,线程在运行的任什么时候刻均可以中止,而后Windows又去调度另外一个线程,你有点控制权,去控制你想运行的线程,可是这控制权很少,不控制为好。对于线程的执行,记住一点:函数
你不能保证本身的线程一直运行,你不能阻止其余的线程的运行。oop
线程优先级别0~31,Windows把线程用从高到低的调度方式轮流调度线程,假若有一个优先级别为31的线程运行结束了,而后Windows会找下一个空闲的线程,若是空闲的线程中有一个级别也是31的线程,那么Windows又会把31级别的线程交给CPU处理。spa
进程优先级类:
Windows支持6个进程优先级类:Idel,Below Normal,Normal,Above Normal,Hight和Realtime(依次向高),其中Normal是默认的进程优先级,因此它是最经常使用的。
Windows支持7个相对线程优先级:Idel,Lowest,Below Normal,Normal,Above Normal,Highest和Time-Critical。
相对线程优先级 |
进程优先级 |
|
||||
Idle |
Below Normal |
Normal |
Above Normal |
High |
Realtime |
|
Time-critical |
15 |
15 |
15 |
15 |
15 |
31 |
Highest |
6 |
8 |
10 |
12 |
15 |
26 |
Above Normal |
5 |
7 |
9 |
11 |
14 |
25 |
Normal |
4 |
6 |
8 |
10 |
13 |
24 |
Below Normal |
3 |
5 |
7 |
9 |
12 |
23 |
Lowest |
2 |
4 |
6 |
8 |
11 |
22 |
Idle |
1 |
1 |
1 |
1 |
1 |
16 |
记住:若是更改一个进程的优先级类,线程的相对优先级不会改变,但它的优先值会改变。
Windows永远都不会调度进程,他调度的只有线程,“进程优先级类”是Microsoft提出的一个抽象概念,目的是为了帮助你理解本身的应用程序和其余正在运行的应用程序的关系,它没有别的用途。
能够更改它的线程相对优先级,Thread中的Priority属性,向它传递ThreadPriority枚举类型中定义的5各值之一,即在上表中的灰色部分列。
多线程并发的一种形式,采用多个线程来执行程序;应用程序几乎不须要自行建立新的线程。你若要为 COM interop 程序建立 STA线程,就得建立线程,这是惟一须要线程的状况。线程是低级别的抽象,线程池是稍微高级一点的抽象,当代码段遵循线程池的规则运行时,线程池就会在须要时建立线程。
线程池ThreadPool
每一个进程都有一个线程池,线程池的默认大小为:每一个可用的处理器有 25 个线程。使用 SetMaxThreads 方法能够更改线程池中的线程数。
ThreadPool类型拥有一个QueueUserWorkItem的静态方法。该静态方法接收一个委托(WaitCallback),表明用户自定义的一个异步操做。
// 一个 System.Threading.WaitCallback,表示要执行的方法。
// 若是此方法成功排队,则为 true;若是未能将该工做项排队,则引起 System.NotSupportedException。
//System.NotSupportedException:承载公共语言运行时的宿主不支持此操做。
[SecuritySafeCritical]
public static bool QueueUserWorkItem(WaitCallback callBack); // 将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。
// System.Threading.WaitCallback,它表示要执行的方法。
// state: 包含方法所用数据的对象。
// 若是此方法成功排队,则为 true;若是未能将该工做项排队,则引起 System.NotSupportedException。
[SecuritySafeCritical]
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
使用线程池的好处:
减小了线程的建立、开始和中止的次数,提升了效率
可以使咱们将注意力放在业务逻辑上而不是多线程架构上。
如下场景是不适合使用线程池,而是手工管理线程:
协做式取消操做模式:不管执行操做的代码,仍是试图取消操做的代码,都必须使用两个类型CancellationToken 和CancellationTokenSource
为取消一个操做必须先建立一个CancellationTokenSource对象,这个对象包含了和管理取消有关的全部状态。
// 摘要:通知 System.Threading.CancellationToken,告知其应被取消。 [ComVisible(false)] public sealed class CancellationTokenSource : IDisposable { // 摘要:初始化 System.Threading.CancellationTokenSource。 public CancellationTokenSource(); // 返回结果:是否已请求取消此 System.Threading.CancellationTokenSource。 public bool IsCancellationRequested { get; } // 返回结果:与此 System.Threading.CancellationTokenSource 关联的 System.Threading.CancellationToken。传递给操做,使那些操做能够取消; // 异常:T:System.ObjectDisposedException:已释放标记源。 public CancellationToken Token { get; } // 摘要:建立一个将在任何源标记处于取消状态时处于取消状态的 System.Threading.CancellationTokenSource。 // token1: 要观察的第一个 System.Threading.CancellationToken。 // token2: 要观察的第二个 System.Threading.CancellationToken。 // 返回结果:一个连接到源标记的 System.Threading.CancellationTokenSource。 // 异常: T:System.ObjectDisposedException:与源标记之一关联的 System.Threading.CancellationTokenSource 已被释放。 public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); // 摘要: 建立一个将在任何源标记处于取消状态时处于取消状态的 System.Threading.CancellationTokenSource。 // tokens: 要观察的 System.Threading.CancellationToken 实例。 // 返回结果:一个连接到源标记的 System.Threading.CancellationTokenSource。 // 异常: T:System.ObjectDisposedException:与源标记之一关联的 System.Threading.CancellationTokenSource 已被释放。T:System.ArgumentNullException: tokens 为 null。 public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); // 摘要:传达取消请求。 // 异常: T:System.ObjectDisposedException:此 System.Threading.CancellationTokenSource 已被释放。 // T:System.AggregateException:聚合异常包含由相关联的 System.Threading.CancellationToken 上已注册的回调引起的全部异常。 public void Cancel(); // 摘要:传达取消请求。 // 参数: throwOnFirstException:指定异常是否应当即传播。 // 异常: T:System.ObjectDisposedException:此 System.Threading.CancellationTokenSource 已被释放。 // T:System.AggregateException:聚合异常包含由相关联的 System.Threading.CancellationToken 上已注册的回调引起的全部异常。 public void Cancel(bool throwOnFirstException); // 摘要:释放 System.Threading.CancellationTokenSource 类的当前实例所使用的全部资源。 public void Dispose(); }
Register方法传递一个Action的委托, 在线程中止时被回调;
private static void CancellingAWorkItem(bool isCanCancel=true) { CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken noneToken = CancellationToken.None;//调用该静态属性,实现禁止操做被取消 // Pass the CancellationToken and the number-to-count-to into the operation ThreadPool.QueueUserWorkItem(o => Count(isCanCancel==true?cts.Token:noneToken, 1000)); Console.WriteLine("Press <Enter> to cancel the operation."); Console.ReadLine(); cts.Cancel(); // If Count returned already, Cancel has no effect on it // Cancel returns immediately, and the method continues running here... Console.ReadLine(); // For testing purposes } private static void Count(CancellationToken token, Int32 countTo) { for (Int32 count = 0; count < countTo; count++) { if (token.IsCancellationRequested) { Console.WriteLine("Count is cancelled"); break; // Exit the loop to stop the operation }
//token.Register(() => { Console.WriteLine("线程被终止"); }); Console.WriteLine(count); Thread.Sleep(200); // For demo, waste some time } Console.WriteLine("Count is done"); }
线程池将本身的线程划分为工做者(worker)线程和I/O线程:工做者线程用于执行计算限制的异步操做,I/O线程用于异步I/O限制操做;
计算限制的异步操做:也称并发编程。容许线程池在多个CPU内核上调度任务,使多个线程能并发工做。
I/O限制的异步操做:也称异步编程。容许将任务交与硬件设备处理,期间不彻底占用线程和CPU资源。而后由线程池线程处理I/O操做结果;
当咱们调用Windows API对I/O如文件进行同步读写时,该线程建立一个IRP的设备请求,并将IRP发送给device stack,而后在核心态等待其完成。而当咱们用异步方式时,该线程发送完IRP后,则返回,继续后续的操做。Windows有不少种方式能够通知该I/O操做的完成,与Thread Pool相关的有两种。一种是将完成notification放在该线程的APC队列中,该队列只有当线程进入等待状态是,才会被读取;而另外一种方式则是I/O Completion Port,咱们能够把这样也认为是个队列,而读取这个队列能够经过GetQueuedCompletionStatus API函数。
在.Net线程池中,I/O Thread实际就是I/O完成端口,而Worker Thread能够当作.Net经过Thread类预先建立的一组线程。.Net及ThreadPool类中提供的方法,如QueueUserWorkItem, Timer, delegate回调等使用的都是Worker Thread。而.Net中对I/O操做的封装,如FileStream, NetworkStream等则是使用的IO Thread。
C#中提到的计算约束Computing-Bound和I/O约束I/O-Bound的操做。当咱们调用FileStream.BeginRead读文件时,BeginRead并无建立新的线程去执行读操做,读操做(IRP)被设备执行,同时该线程继续执行其余任务。而当设备完成读操做后,线程池(IO Thread)中的一个线程开始执行回调函数。
而当咱们执行的是Computing-Bound的操做时,开始咱们就新建立一个线程或经过ThreadPool.QueueUserWorkItem使用线程池中的线程来执行操做,任务完成后,这个线程会执行回调函数。