标签:logs 服务 共享 维护 冻结 包含 决定 逻辑 模式
最近在总结多线程、CLR线程池以及TPL编程实践,重读一遍CLR via C#,比刚上班的时候收获还是很大的。还得要多读书,读好书,同时要多总结,多实践,把技术研究透,使用好。
话不多说,直接上博文吧。先说一下,为什么Windows要支持线程机制?
1. Windows为什么要支持线程
计算机的早期时代,操作系统没有线程的概念,整个系统只运行着一个执行线程,其中包含操作系统代码和应用程序代码。只用一个执行线程的问题在于,长时间运行的任务会阻止其他任务的执行。例如16位Windows的时代,打印文档的应用程序很容易“冻结”整个机器。
Microsoft 在设计Windows NT这个版本的OS内核时,决定在一个进程中运行应用程序的每个实例。进程实际是应用程序的实例要使用的资源的集合。每个进程都被赋予了一个虚拟地址空间,确保一个进程中使用的代码和数据无法由另一个进程访问。这就确保了应用程序实例的健壮性。同时,进程访问不了OS的内核代码和数据;所以,应用程序代码破坏不了操作系统的代码和数据
如果应用程序发生死循环会发生什么?如果机器只有一个CPU,它会执行死循环,不能执行其他任何程序。Microsoft 的解决方案就是线程。作为一个Windows概念,线程的职责是对CPU进行虚拟化。Windows为每个进程都提供了该进程专用的线程(功能相当于一个CPU)。应用程序的代码进行死循环,与代码关联的进程会“冻结”,但其他进程(它们有自己的线程)不会冻结,它们会继续执行。
线程很强大,因为它们使Windows即使在执行长时间运行的任务时,也能随时响应。
但是,和一切虚拟化机制一样,线程有空间(内存消耗)和时间(运行时的执行性能)的开销。
2. 线程开销有哪些?
每个线程都有以下要素组成:
线程内核对象(thread kernel object):线程的描述属性和线程上下文,上下文是包含CPU寄存器集合内存块。对于x86、x64、ARM CPU架构,线程上下文分别使用约700,1240和350字节的内存。
线程环境块(thread environment block,TEB):用户模式(应用程序代码能快速访问的地址空间)中分配和初始化的内存块。TEB耗用一个内存页( x86、x64、ARM CPU 中是4KB)
用户模式栈(user-mode stack):用户模式栈存储传给方法的局部变量和实参,它还包含一个地址:指出当前方法返回时,线程应该从什么地方接着执行。Windows默认为每个线程的用户模式栈分配1MB内存。Windows只是保留1MB地址空间,在线程实际需要时才会提交(调拨)物理内存。
内核模式栈(kernel-mode stack):应用程序代码向操作系统中的内核模式传递参数时,还会使用内核模式栈,出于安全的考虑,Windowd会把这些实参从线程的用户模式栈复制到线程的内核模式栈。32windows 内核模式栈大小12KB,64位是24KB
DLL线程连接(Attach)和线程分离(Detach)通知:Windows的一个策略是,任何时候在进程中创建线程,都会调用进程中加载的所有非托管DLL的DllMain方法,并向该方法传递DLL_THREAD_ATTACH标志。同样的,任何时候线程终止,都会调用进程中的所有非托管DLL的DllMain方法,并向该方法传递DLL_THREAD_DETACH标志。
上下文切换
单CPU计算机一次只能做一件事情。所以,Windows必须在操作系统中的所有线程(逻辑CPU)之间共享物理CPU。
Windows任何时刻只将一个线程分配给一个CPU。那个线程能运行一个“时间片”的长度。时间片到期,Windows就将上下文切换到另一个线程。每次上下文切换都要求Windows执行一下操作:
当Windows上下文切换到另一个线程时,会产生一定的性能损失。
一个时间片结束时,如果Windows决定再次调度同一个线程(而不是切换到另一个线程),那么Windows不会执行上下文切换。
3. 使用线程的理由:什么场景下使用线程
可响应性(通常是针对客户端GUI应用程序):客户端GUI应用程序中,可以将一些工作交给一个线程进行,使GUI线程能灵敏的响应用户的输入。在这个过程中创建的线程数可能超过CPU的核心数,会浪费系统资源和降低性能。但是用户体验得到改善和增强。
性能(对于客户端和服务端应用程序):由于Windows每个CPU调度一个线程,而且多个CPU能并发执行这些线程,所以同时执行多个操作可以提升性能。
4. CLR线程池机制
创建和销毁线程是一个昂贵的操作,要耗费大量的时间,同时,太多的线程会浪费内存资源。由于操作系统必须调度可运行的线程并执行上下文切换,所以太多的线程会影响性能。
为了改善这个情况,CLR提供了线程池机制,每个CLR一个线程池。
CLR线程池并不会在CLR初始化的时候立刻建立线程,而是在应用程序要创建线程来执行任务时,线程池才初始化一个线程。线程的初始化与其他的线程一样。在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请
求时,线程池里挂起的线程就会再度激活执行任务。
这样既节省了创建线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。
5. 进程、线程和应用程序域
进程(Process):Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。
应用程序域(AppDomain):一组程序集的逻辑容器。CLR在初始化在初始化时创建第一个AppDomain(默认AppDomain),这个AppDomain在进程终止时被销毁。.NET的程序集正是在应用程序域中运行的。
一个进程可以包含有多个应用程序域,一个应用程序域也可以包含多个程序集。
在一个应用程序域中包含了一个或多个上下文context,使用上下文CLR就能够把某些特殊对象的状态放置在不同容器当中
线程(Thread):进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。
线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
6. 进程、线程和应用程序域的关系
进程、应用程序域、线程的关系如下图,
一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。
周国庆
2017/5/26
标签:logs 服务 共享 维护 冻结 包含 决定 逻辑 模式
原文地址:http://www.cnblogs.com/tianqing/p/6906934.html