最近在总结多线程、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执行一下操作:

  • 将CPU寄存器的值保存到当前正在运行的线程的内核内部的一个上下文结构中
  • 从现有线程集合中选出一个线程供调度
  • 将所选上下文结构中的值加载到CPU寄存器中

当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

线程机制、CLR线程池以及应用程序域的更多相关文章

  1. 基础线程机制--Executor线程池框架

    基础线程机制 Executor线程池框架 1.引入Executor的原因 (1)new Thread()的缺点 ​  每次new Thread()耗费性能 ​  调用new Thread()创建的线程 ...

  2. C# 多线程学习系列三之CLR线程池系列之ThreadPool

    一.CLR线程池 1.进程和CLR的关系一个进程可以只包含一个CLR,也可以包含多个CLR2.CLR和AppDomain的关系一个CLR可以包含多个AppDomain3.CLR和线程池的关系一个CLR ...

  3. 浅谈线程池(上):线程池的作用及CLR线程池

    原文地址:http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html 线程池是一个重要的概念. ...

  4. 死锁、Lock锁、等待唤醒机制、线程组、线程池、定时器、单例设计模式_DAY24

    1:线程(理解) (1)死锁 概念: 同步中,多个线程使用多把锁之间存在等待的现象. 原因分析: a.线程1将锁1锁住,线程2将锁2锁住,而线程1要继续执行锁2中的代码,线程2要继续执行锁1中的代码, ...

  5. 【Java 并发】Executor框架机制与线程池配置使用

    [Java 并发]Executor框架机制与线程池配置使用 一,Executor框架Executor框架便是Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下 ...

  6. 补充:垃圾回收机制、线程池和ORM缺点

    补充:垃圾回收机制.线程池和ORM缺点 垃圾回收机制不仅有引用计数,还有标记清除和分代回收 引用计数就是内存地址的门牌号,为0时就会回收掉,但是会出现循环引用问题,这种情况下会导致内存泄漏(即不会被用 ...

  7. CLR线程池

    WaitCallback 表示要在 ThreadPool 线程上执行的回调方法. 创建委托,方法是将回调方法传递给 WaitCallback 构造函数. 您的方法必须具有此处所显示的签名. 如果想使用 ...

  8. CLR线程概览(下)

    作者:施懿民链接:https://zhuanlan.zhihu.com/p/20866017来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 同步: 托管代码 托管代码可 ...

  9. CLR线程概览(一)

    托管 vs. 原生线程 托管代码在“托管线程”上执行,(托管线程)与操作系统提供的原生线程不同.原生线程是在物理机器上执行的原生代码序列:而托管线程则是在CLR虚拟机上执行的虚拟线程. 正如JIT解释 ...

随机推荐

  1. ATM取款小项目

    项目要求: 1.用户需要从控制台输入账号密码,账号或者密码不正确报异常 2.每日取款的金额有限制(100,30000),否则报异常 3.每次取款都要有记录,并在下一次取款时显示出来 思路: 1.先在& ...

  2. 跟着刚哥梳理java知识点——泛型(十三)

    一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: public class GenericTest { public static void main(String[] a ...

  3. Centos7:利用crontab定时执行任务

    cron服务是Linux的内置服务,但它不会开机自动启动.可以用以下命令启动和停止服务: /sbin/service crond start /sbin/service crond stop /sbi ...

  4. 使用nodejs进行WEB开发

    这里,准备从零开始用nodejs实现一个微博系统.功能包括路由控制.页面模板.数据库访问.用户注册.登录.用户会话等内容. 将会介绍Express框架.MVC设计模式.ejs模板引擎以及MongoDB ...

  5. js 解析本地Excel文件!

    通常,一般读取Excel都是由后台来处理,不过如果需求要前台来处理,也是可以的.. 1.需要用到js-xlsx,下载地址:js-xlsx 2.demo: <!DOCTYPE html>&l ...

  6. iOS 播放GIf图, 动态效果

    一.如果你集成了SDWebImage , 有一个很简单的方法 //导入sdwebImage的某个头文件 #import "UIImage+GIF.h" _bubble1.backg ...

  7. DirectFB学习笔记一

    本文记录directfb程序的基本操作流程. 1.首先创建一个directfb对象:DirectFBInit(&argc,&argv)初始化然后创建DirectFBCreate(&am ...

  8. Silverlight的DataGrid合并单元格

    现在也不知道还有没有同学做Silverlight开发了,我是一个Silverlight菜鸟,遇到问题也很难百度查到.就简单的记录一下这两天遇到的问题,并做了一个简单的小Demo,希望能够帮助到其他同学 ...

  9. Vue项目的部署

    通过vue-cli创建的工程,默认已经打好了基础,包含vue-loader webpack. 通常我们开发中,使用npm run dev进行开发,webpack会Hot reload,不用我们手动刷新 ...

  10. 被低估的选手 - JavaFx

    被低估的选手 - JavaFx 1.MFC(Visual C++) 个人不是很喜欢这个框架,太多系统定义的东西,就像无底洞,学都学不完,这个东西需要你有比较强的记忆力,并且能融会贯通里面很多预定义的功 ...