《CLR via C#》读书笔记 之 线程基础

第二十五章 线程基础

2014-06-28

25.1 Windows为什么要支持线程

25.2 线程开销

25.3 停止疯狂

25.6 CLR线程和Windows线程

25.7 使用专用线程执行异步的计算限制操作

25.8 使用线程的理由

25.9 线程调度和优先级

25.10 前台线程和后台线程

参考 

25.1 Windows为什么要支持线程



返回

Microsoft设计OS内核时,他们决定在一个进程(process)中运行应用程序的每个实例。进程不过是应用程序的一个实例要使用的资源的一个集合。每个进程都赋予了一个虚拟地址空间,确保一个进程使用的代码和数据无法由另一个进行访问。这样就确保了应用程序集的健壮性,因为一个进程无法破坏另一个进程里的数据和代码。另外,进程是无法访问到OS的内核代码和数据。

如果一个应用程序进入死循环时,如果只是单核的CPU的,它会无限循环执行下去,不能执行其他代码,这样会使系统停止响应。对此,Microsoft拿出的一个解决方案——线程。线程的职责就是对CPU的虚拟化。Windows为每个进程都提供了该进程专用的线程(功能相当于一个CPU,可将线程理解成一个逻辑CPU)。如果应用程序的代码进入无限循环,与那个代码关联的进程会"冻结",但其他进程不会冻结,会继续执行。

25.2 线程开销



返回

线程尽管非常强悍,但和一切虚拟化机制一样,线程会产生空间(内存耗用)和时间(运行时的执行性能)上的开销。

  • 线程内核对象(thread kernel object)    OS为系统中创建的每个线程都分配并初始化这种数据结构。在该数据结构中,包含一组对线程进行描述的属性。 数据结构中还包含所谓的线程上下文(thead context)。上下文是一个内存块,其中包含了CPU的寄存器集合。Windows在一台x86CPU的计算机运行时,线程上下文使用约700字节的内存。对于x64和IA64CPU,上下文分别使用约1240字节和2500字节的内存。
  • 线程环境块(thread environment block,TEB)    TEB是在用户模式中分配和初始化的一个内存块。TEB耗用1个内存页(x86和x64CPU中是4KB,IA64CPU中是8K)。TEB包含线程的异常处理链首。线程进入的每个try块都在链首插入一个节点。线程退出try块时,会从链中删除该节点。除此之外,TEB还包括线程的"线程本地存储"数据,以及由GDI和OpenGL图形使用的一些数据结构。
  • 用户模式栈(user-mode stack)    用户模式栈用于存储传给方法的局部变量和实参。它还包含一个地址:指向当前方法返回时,应该接着从哪个地址开始执行。默认情况下,Windows为每个线程的用户模式分配1MB的内存。
  • 内核模式栈(kernel-model stack)    应用程序代码向OS中的一个内核模式的函数传递实参时,会使用内核模式栈。出于安全方面的原因,针对从用户模式的代码传给内核的任何实参,Windows都会把它们从线程的用户模式栈复制到线程的内核模式栈。一经复制,内核就可以验证实参的值,然后进行处理。除此之外,内核会调用它自己内部的方法,并利用内核模式栈传递自己的实参、存储函数的局部变量以及存储返回地址。在32为的Windows运行时,内核模式栈大小为12KB;在64位Windows上运行时,大小为24KB。
  • DLL线程连接(attach)和线程分离(detach)通知 Windows的一个策略是,任何时候在进程中创建一个线程,都会调用那个进程中加载的所有DLL的DLLMain方法,并向该方法传递一个DLL_THREAD_ATTACH标识。类似的,任何时候一个线程终止,都会调用进程中的所有DLL的DLLMain方法,并向该方法传递一个DLL_THREAD_DETACH标识。有的DLL需要利用这些通知,为进程中创建和销毁的每个线程执行一些特殊的初始化或资源清理操作。

注意:C#和其他大多数托管编程语言生成的DLL没有DllMain函数,所有不会接到通知,这提升了性能。

单CPU每次只能做一件事情,所以,Windows必须在系统中的所有线程之间共享CPU。在给定的时刻,Windows只将一个线程分配给CPU。那个线程允许运行一个“时间片”。一旦时间片到期,Windows就上下文切换到另一个线程。每次上下文切换都要求Windows做以下操作:

  1. 将CPU寄存器中的值保存到当前正在运行的线程的内核对象内部的一个上下文结构中。
  2. 从现有线程集合中选出一个线程供调度(这个就是要切换到的线程)。如果该线程由另一个进程拥有,Windows在开始执行任何代码或者任何数据之前,还必须切换CPU"看见"的虚拟地址空间。
  3. 将所选上下文结构中的值加载到CPU的寄存器中。

上下文切换完成后,CPU执行所选的线程,直到它的时间片到期。然后,会发生另一次上下文切换。Windows大约每30毫秒执行一次上下文切换。上下文切换是净开销;也就是说,上下文切换所产生的开销不会换来任何内存或性能上的收益。Windows执行上下文切换,向用户提供一个健壮的、响应灵敏的操作系统。

事实上,上下文切换对性能的影响可能超出你的想象:

  • CPU现在是要执行一个不同的线程,而之前的线程代码和数据还保存在CPU的高速缓存中,这使CPU不必进程访问RAM。当Windows上下文切换到一个新的线程时,这个新线程极有可能要执行不同的代码和数据,这些数据不再CPU的高速缓存中,因此,CPU必须访问RAM来填充它的高速缓存,以恢复告诉执行状态。但是,在30毫秒之后,一次新的上下文切换又发生了。
  • 除此之外,执行垃圾回收时,CLR必须挂起所有线程,遍历它们的栈来查找根以便对堆中的对象进行标记,再次遍历它们的栈,再次恢复所有线程。所以,减少线程的数量也会显著提升垃圾回收器的功能。

根据上述讨论,我们的结论是必须尽可能地避免使用线程,因为它们要耗用大量内存,而且需要相当多的时间来创建、销毁和关联。WIndows在进行上下文切换,以及垃圾回收时也会浪费更多的时间。但是不可否认,因为才是Windows变得更健壮,反应更灵敏。
应该指出,安装多个CPU的计算机可以真正同时允许几个线程,这提升应用程序的可伸缩性(在更少的时间内做更多的事)。Windows为每个CPU内核都分配一个线程,每个内核都自己执行到其他线程的上下文切换。Windows确保单个线程不会同时在多个内核上调度。

25.3 停止疯狂



返回

如果追求性能,那么任何计算机最优的线程数就是那台计算机的CPU个数。如果线程数超过了CPU的个数那么就会发生线程上下文切换和性能损失。

在Windows中,创建一个进程的代价是昂贵的。创建一个进程通常要花几秒钟的时间,必须分配大量的内存,这些内存必须初始化,EXE和DLL文件必须从磁盘上加载等等。相反,在Windows创建线程是十分廉价的。所以,开发人员决定停止创建进程,改为创建线程。这就是我们看到有这么多线程的原因。但是,线程相对于其它系统资源还是比较昂贵的,所以还是应该省着用。

必须承认,系统中的大多数线程都是本地代码创建的。所以,线程的用户模式栈仅仅保留(预定)地址空间,而且极有可能没有完全提交来获取物理内存。然而,随着越来越多的应用程序成为托管应用程序,或者在其中运行托管组件,会有越来越多的栈被完全提交,会真实的分配到1MB的物理内存。无论如何,即使抛开用户模式栈不谈,所有线程仍然会分配到内核模式栈以及其它资源。这种觉得线程十分廉价便胡乱创建线程的势头必须停止。

25.6 CLR线程和Windows线程



返回

CLR使用的是Windows的线程处理能力。虽然今天,CLR线程直接对应于一个Windows线程,但Mircrosoft CLR团队保留了将来把它从Windows线程分离的权利。有一天,CLR可能引入它自己的逻辑线程,使一个逻辑线程并非映射到一个物理Windows线程。据说,逻辑线程将使用比物理线程少得多的资源,所以能在极少量的物理线程上运行大量的逻辑线程。遗憾的是,CLR团队还没有推出这个功能。

对你来说,这一切意味着在操纵线程时,代码应尽可能少地做出一些假设。例如,应避免P/Invoke本地Windows函数,因为这些函数对CLR线程一无所知。通过避免使用本地Windows函数,坚持使用FCL中的类型,将来性能的到提升之后,你的代码马上就能享受到这种提升。

备注:如果想P/Invoke本地代码,而且代码必须使用当前物理操作系统的线程来执行,那么应该调用System.Threading.Thread的静态BeginThreadAffinity方法。BeginThreadAffinity就是告诉CLR不要切换线程。线程不再需要使用物理操作系统线程运行时,可调用Thread的EndThreadAffinity方法来通知CLR。

25.7 使用专用线程执行异步的计算限制操作



返回

本节将展示如何创建一个线程,并让它执行一次异步计算限制操作。虽然会教你具体如何做,但是强烈建议你避免采用这里展示的技术。相反,应该尽量使用CLR的线程池来执行异步计算限制操作,具体以后会讨论。

如果执行的代码要求处于一种特定的状态,而这种状态对于线程池的线程来说是非比寻常的,就可以考虑创建一个线程 。例如,满足以下任意一个条件,就可以显式创建自己的线程:

  • 线程需要以非普通线程优先级运行。所有线程池线程都以普通优先级运行;虽然可以改变这种优先级,但不建议这样做。另外,在不同的线程池操作之间,对优先级的更改是无法持续的。
  • 需要线程表现为一个前台线程,防止应用程序在线程结束它的任务之前终止。线程池的线程都是后台线程。如果CLR想要终止线程,它们可能被迫无法完成任务。
  • 一个计算限制的任务需要长时间运行。线程池为了判断是否需要创建一个额外的线程,所采用的逻辑是比较复杂的。直接为长时间运行的任务创建一个专用线程,则可以避免这个问题。
  • 要启动一个线程,并可能调用Thread的Abort方法来提前终止它。

为了创建一个专用线程,要构造一个System.Threading.Thread类的一个实例,向它传递方法的名称,它的构造器如下:

1 public sealed class Thread : CriticalFinalizerObject, _Thread
2 {
3     public Thread(ParameterizedThreadStart start){ }
4     //这里没有列出一些不常用的构造器
5 }

ParameterizedThreadStart委托的签名如下:

public delegate void ParameterizedThreadStart(object obj);

下面的代码演示如何创建一个专用线程,让它异步调用一个方法:

 1         public void AsynchronousThreadDemo()
 2         {
 3             Console.WriteLine("Main thread: starting a dedicated thread " +
 4                 "to do an asynchronous operation");
 5             Thread dedicateThread = new Thread(ComputeBoundOp);
 6             dedicateThread.Start(5);
 7
 8             Console.WriteLine("Main thread: Doing other work here...");
 9             Thread.Sleep(5000);
10
11             dedicateThread.Join();//等待线程终止
12             Console.WriteLine("Main exit");
13         }
14
15         private void ComputeBoundOp(object state)
16         {
17             //这个个方法由一个专用线程执行
18             Console.WriteLine("In ComputeBoundOp: state={0}", state);
19             Thread.Sleep(1000);//模拟其他任务(1秒)
20             //这个方法返回后,专用线程终止
21         }

运行程序,可能得到下面的输出:

Main thread: starting a dedicated thread to do an asynchronous operation
Main thread: Doing other work here...
In ComputeBoundOp: state=5
Main exit

但有的时候运行上述代码,也可能得到以下结果,因为我无法控制Windows对两个线程进行调度的方式:

Main thread: starting a dedicated thread to do an asynchronous operation
In ComputeBoundOp: state=5
Main thread: Doing other work here...
Main exit

25.8 使用线程的理由



返回

  • 可以使线程将代码同其他代码隔离。这将提高应用程序的可靠性。事实上,这也正是Windows在操作系统中引入线程概念的原因。Windows之所以要用线程来获得可靠性,是因为你的应用程序对于操作系统来说是第三方组件,而Microsoft不会在你发布程序之前对这些代码进行验证。
  • 可以使用线程来简化编码 。有时候,如果通过一个任务自己的线程来执行该任务,编码会变得更简单。但是,如果这样做,肯定要使用额外的资源。代码不是十分“经济”。
  • 可以用线程来实现并发执行。如果知道自己的应用程序要在多CPU的机器上运行,那么让多个任务同时运行,就能提高性能。现在多核CPU相当普遍,所以设计应用程序来使用多个内核是有意义的。

25.9 线程调度和优先级



返回
 
抢占式(preemptive)操作系统必须使用某种算法判断在什么时候调度哪些线程多长时间。本节讨论Windows采用的算法。在前面,已经提到过每个线程的内核对象都包含一个上下文结构。上下文结构反映了当线程上一次执行时,线程的CPU寄存器的状态。在一个时间片之后,Windows检查现有的所有线程内存对象。在这些对象中,只有那些没有正在等待什么的线程才适合调度。Windows选择一个可调度的线程内核对象,并上下文切换到它。Windows实际记录了每个线程被上下文切换到的次数。可以使用向Microsoft Spy++(Visual studio的一个小工具)这样的工具查看这个数据。

Windows之所以被称为一种抢占式多线程操作系统,是因为线程可以在任何时间被停止(被抢占),并调度另一个线程。所以,你不能保证自己的线程一直在运行,不能阻止其他线程的运行。

每个线程都分配了从0(最低)到31(最高)的优先级。系统决定将哪个线程分配给一个CPU时,它首先检查优先级为31的线程,并以一种轮流的方式调度它们。如果优先级为31的线程是可调度的,就把它分配给一个CPU。这个线程的时间片结束时,系统检查是否有另一个优先级为31的线程可以运行;如果是,就允许将那个线程分配给一个CPU。

只要系统中存在一个可调度的优先级为31的线程,系统就永远不会将优先级0~30的任何线程分配给CPU。这种情况称为饥饿(starvation)。

较高优先级的线程总是抢占较低优先级的线程,例如:一个优先级为5的线程正在运行,而系统确定一个较高优先级的线程准备好运行,系统会立即挂起(暂停)较低优先级的线程(即使后者的时间片还没有用完),将CPU分配给较高优先级的线程,该线程将获得一个完整的时间片。

顺便说一下,系统启动时,会创建一个零页线程(zero page thread)的特殊线程。这个线程的优先级为0,而且是整个系统中唯一一个优先级为0的线程。零页线程负责在没有其他进程需要执行时,将系统的RAM的所有空闲页清零。

设计应用程序时,应决定自己的应用程序是需要比机器上同时运行的其他应用程序更大还是更小的响应能力。然后选择一个进程优先级类(priority class)来反映你的决定。Windows支持6个进程优先级类:Idle,Below Normal,Normal,Above Normal,High和Realtime。Normal是默认的进程优先级类,所以它也是最常用的进程优先级类。一个应用程序(比如屏幕保护程序)在系统什么事情都不做的时候运行,就适合分配Idle优先级类。只有在绝对必要时才使用High优先级类。Realtime优先级类要尽可能避免,它的优先级相当高,甚至可能干扰操作系统任务,除了需要响应延迟(latency)很短的硬件事件,或一些执行不能中断的非常“短命”的任务。

选好一个优先级类之后,就不要再思考你的应用程序和其他应用程序的关系了。现在,要把注意力放在应用程序中的线程上。Windows支持7个相对线程优先级:Idle,Lowest,Below Normal,Normal,Above Normal,Highest和Time-Critical。这些优先级是相对进程优先级类的。同样,Normal是默认的优先级。

总之,你的进程是一个优先级类的成员。在你的进程中,要为各个线程分配相对优先级。事实上,0~31的线程优先级,是由进程的优先级类和其中的一个线程的相对优先级映射而来的。下图展现了这种映射关系:


线程相对

优先级


进程优先级类


Idle


Below Normal


Normal


Above Normal


High


Real-Time


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

请注意,表中线程优先级没有为0的。这是因为0优先级保留给零页线程了,系统不允许其他线程的优先级为0。而且,以下优先级也是不可获得的:17,18,19,20,21,27,28,29和30。当然,如果编写的是运行在内核模式的设备却、驱动程序,可以获得这些优先级。
注意:"进程优先级类"的概念容易引起一些混淆。人们可能认为这意味着Windows能调度进程。然而,Windows永远不会调度进程;它调度的只有线程。"进程优先级类"是Microsoft提出的一个抽象概念,旨在帮助你理解自己的应用程序和其它正在运行应用程序的关系,它没有其它用途。
提示:最好是降低一个线程的优先级,而不是提升另一个线程的优先级。
在你的应用程序中可以更改它的线程的相对线程优先级,这需要设置Thread的Priority属性,向它传递ThreadPriority枚举类型中定义的5个值之一,即Lowest(最低),Below Normal(低于标准),Normal(标准),Above Normal(高于标准),Highest(最高)。CLR为自己保留了Idle和Time-Critical优先级。

应该指出的是,System.Diagnostics命名空间包含一个Process类和一个ProcessThread类。这两个类分别提供了进程和线程的Windows视图。应用程序需要以特殊的安全权限运行才能使用这两个类。例如,在Silverlight应用程序或者ASP.NET应用程序中,就不可以使用这两个类。
另一方面,应用程序可使用AppDomain和Thread类,它们公开了AppDomain和线程的CLR视图。一般不需要特殊安全权限来使用这两个类,虽然某些操作仍需要提升权限才可以。

25.10 前台线程和后台线程  



返回

CLR将每个线程要么视为前台线程,要么视为后台线程。一个进程中的所有前台线程停止时,CLR会强制终止仍然在运行的任何后台进行。这些后台进程被直接终止,不会抛出异常。
因此,前台进程应该用于执行确实想完成的任务,比如将数据从内存缓存区fluch到磁盘。另外,应该为非关键的任务使用后台线程,比如重新计算电子表格的单元格,或者为记录建立索引。这是由于这些工作能在应用程序重启时继续,而且如果用户终止应用程序,就没有必要强迫它保持活动状态。
CLR要提供前台线程和后台线程的概念来更好地支持AppDomain。每个AppDomain都可以运行一个单独的应用程序,每个应用程序都有它自己的前台线程。如果一个应用程序退出,造成它的前台线程终止,则CLR仍然需要保持活动并运行,使其他应用程序继续运行。所有应用程序都退出,它们的所有前台线程都终止后,整个进程就可以被销毁了。
在一个线程的生存期,任何时候可以从前台变成后台,或者从后台变成前台。应用程序的主线程以及通过构造一个Thread对象来显式创建的任何线程都默认为前台线程。另一方面,线程池默认为后台线程。此外,由进入托管执行环境的本地(native)代码创建的任何线程都被标记为后台线程。
下面的代码演示了前台线程和后台线程的差异:

 1         static void Main()
 2         {
 3             //创建一个新线程(默认为前台线程)
 4             Thread t = new Thread(Worker);
 5
 6             //是线程成为一个后台线程
 7             t.IsBackground = true;
 8
 9             //启动线程
10             t.Start();
11
12             //如果t是一个前台线程,则应用程序大约10秒后才终止
13             //如果t是一个后台线程,则应用程序立即终止
14             Console.WriteLine("Return form Main.");
15         }
16         private static void Worker()
17         {
18             Thread.Sleep(10000); //模拟工作10秒
19
20             //下面这一行代码,只有由一个前台线程执行时,才会显示出来
21             Console.WriteLine("Return form Worker.");
22         }

重要提示:要尽量避免使用前台线程。作者有一次接受一个顾问工作,有个应用程序就是不终止。花了几小时研究问题后,才发现是一个UI组件显示的创建了一个前台线程(默认),这正是进程一直不终止的原因。后来修改组件用了线程池,从而解决了问题。执行效率也提升了

参考

[1] 《CLR via C#》笔记——线程基础 http://www.cnblogs.com/xiashengwang/archive/2012/07/20/2601108.html

《CLR via C#》读书笔记 之 线程基础

时间: 2024-08-07 20:58:01

《CLR via C#》读书笔记 之 线程基础的相关文章

深入理解计算机系统读书笔记一 ---> 计算机基础漫游

一.程序编译的不同阶段. 通常我们是以高级程序开发易于阅读的代码,我们通过语法规则推断代码的具体含义.但是计算机执行代码的时候就需要把代码解析成既定的可执行问题,计算机是如何处理的呢?这里以C语言hello.c文件为例来说明中间过程. #include <stdio.h> int main() { printf("hello world!\n"); } 先上张图. C语言源程序----预处理解析头文件和函数  --- 编译器解析成汇编语言 ---   翻译机器语言指令,打包

我的读书笔记(线程进程)

线程有时候可以被称为微进程或轻量级进程,它的概念和进程十分相似,是一个可以被调度的单元,并且维护自己的堆栈和上下文环境,线程是附属进程的,一个进程可以包含1个或者多个线程,并且同一进程内的多个线程共享一块内存快和资源,一个线程是一个操作系统可调度的基本单元,但同时它的调度受限于包含该线程的进程,也就是说操作系统首先决定了下一个执行的进程,进而才会调度该进程内的线程 线程和进程最大的区别在于隔离性的问题,每个进程都被单独地隔离,拥有自己的内存快,独占的资源及运行数据,一个进程的崩溃不会影响到其他进

【读书笔记】性能测试基础

读书笔记:<零成本实现Web性能测试>第1~3章随手记 第1章 性能测试基础 性能测试的基本流程: 1.明确性能测试需求 2.制定性能测试方案 性能测试方案应该详尽地描述如何进行性能测试,其中应该至少包括: 1)测试背景 2)测试目的 3)测试范围           4)测试进入条件           5)测试退出条件 6)测试指标要求 7)测试策略 8)测试时机 9)测试风险 10)测试资源      3.编写性能测试案例 4.执行性能测试案例 5.分析性能测试结果 6.生成性能测试报告

(CLR via C#学习笔记)异步操作 - 线程池

一 线程池基础 1.线程池维护了一个操作请求队列,将请求的操作追加到线程池队列中,线程池的代码从队列中提取操作项,派发给线程池中的线程; 2.CLR初始化时,线程池中是没有线程的,当有操作派发给线程池时,如果线程池中没有线程或者没有空闲状态的线程,将会创建一个新的线程执行派发的操作,如果有空闲状态的线程,将直接派发一个空闲状态的线程执行操作; 3.线程池线程完成操作任务后,线程不会被销毁,而是返回线程池,进入空闲状态,等待响应另一个派发请求;4.当一个线程池线程处于空闲状态一段时间后(不同的CL

Clr Via C#读书笔记---I/O限制的异步操作

widows如何执行I/O操作      构造调用一个FileStream对象打开一个磁盘文件-----FileStream.Read方法从文件中读取数据(此时线程从托管代码转为本地/用户模式代码)----Read在内部调用win32ReadFile函数-----ReadFile分配一个小的数据结构(I/O请求包,简称IRP)----IRP请求结构初始化(包括:一个文件句柄,文件一个偏移量,一个byte[]数组地址,要传输的字节数,以及其他常规性内容)------初始化后ReadFile将线程从

Clr Via C#读书笔记---线程基础

进程与线程 进程:应用程序的一个实例使用的资源的集合.每个进程都被赋予了一个虚拟地址空间. 线程:对CPU进行虚拟化,可以理解为一个逻辑CPU. 线程要素 线程包括以下要素: 1. 线程内核对象, 其中包含 1)一组对线程进行描述的属性 2)线程上下文,即包含CPU寄存器的集合的一个内存块 2. 线程环境块,在用户模式中分配和初始化的一个内存块,其中包含 1)线程的异常处理链首 2)线程的"线程本地存储数据" 3)由GDI和OpenGL图形使用的一些数据结构 3. 用户模式栈 1)存储

Clr Via C#读书笔记----基元线程同步构造

重点在于多个线程同时访问,保持线程的同步. 线程同步的问题: 1,线程同步比较繁琐,而且容易写错. 2,线程同步会损害性能,获取和释放一个锁是需要时间. 3,线程同步一次只允许一个线程访问资源. 类库和线程安全, 一个线程安全的发那个发意味着两个线程试图同时访问数据时,数据不会被破坏. 基元用户模式和内核模式构造 基元:指代码中最简单的构造,有两种基元构造:用户模式和内核模式. 1,基元用户模式比基元内核模式速度要快,因为直接使用特殊的cpu指令来协调线程,在硬件中发生的. 2,基元用户模式构造

Clr Via C#读书笔记---垃圾回收机制

#1 垃圾回收平台的基本工作原理: 访问一个资源所需的具体步骤: 1)调用IL指令newobj,为代表资源的类型分配内存.在C#中使用new操作符,编译器就会自动生成该指令.2)初始化内存,设置资源的初始状态,使资源可用.类型的实例构造器负责设置该初始状态.3)访问类型的成员(可根据需要反复)来使用资源.4)摧毁资源的状态以进行清理.正确清理资源的代码要放在Finalize, Dispose和Close方法.5)释放内存.垃圾回收器独自负责这一步. 托管堆如何知道应用程序不再用一个对象? 托管堆

Clr Via C#读书笔记---计算限制的异步操作

线程池基础 1,线程的创建和销毁是一个昂贵的操作,线程调度以及上下文切换耗费时间和内存资源. 2,线程池是一个线程集合,供应你的用程序使用. 3,每个CLR有一个自己的线程池,线程池由CLR控制的所有的AppDomain共享. 4,CLR初始化的时候,线程池没有线程的. 5,线程池维护一个操作请求队列.当应用程序想要执行一个一步操作的时候,就调用某个方法.将记录项(empty)追加到线程池队列中,然后线程池代码从队列中提取这个记录项,然后将记录项派遣(dispatch)给一个线程池的线程.当线程