1、线程基础

1.1、线程职责

 线程的职责是对CPU进行虚拟化。Windows 为每个进程豆提供了该进程专用的线程(功能相当于一个CPU)。应用程序的代码进入死循环,于那个代码关联的进程会“冻结”,但其他进程不会冻结,它们会继续执行!

1.2、线程开销

 和一切虚拟化机制一样,线程有空间(内存耗用)和时间(运行时的执行性能)上的开销。

 每个线程都有以下要素。

  • 线程内核对象(Thread Kernel Object)

OS 为系统中创建的每个线程都分配并初始化这种数据结构之一。数据结构包含一组对线程进行描述的属性。数据结构还包含所谓的线程上下文(Thread Context)。上下文是包含CPU寄存器集合的内存快。对于x86,x64ARM CPU架构,线程上下文分别使用约700,1240,和350字节的内存。

  • 线程环境块(Thread Environment Block,TEB)

TEB是在用户模式(应用程序代码能快速访问的地址空间)中分配和初始化的一个内存块。TEB耗用1个内存页(x86x64ARM CPU中是4KB)。TEB包含线程的异常处理链首(head)。线程进入的每个try块都在链首插入一个节点。线程退出try块时,会冲链中删除该节点。除此之外,TEB还包含线程的“线程本地存储”数,以及有GDI(Graphics Device Interface,图形设备接口)和OpenGL图形使用的一些数据结构。

  • 用户模式栈(User-Mode Stack)

用户模式栈用于存储传给方法的局部变量实参。它还包含一个地址;指出当前方法返回时,线程接着应该从什么地方开始执行。默认情况下,Windows为每个线程的用户模式栈分配1MB内存。更具体的说,Windows 只是保留1MB 的地址空间,在线程实际需要时才会提交物理内存。

  • 内核模式栈(Kernel-Mode Stack)

应用程序代码向操作系统的一个内核模式的函数传递实参时,还会使用内核模式栈。出于安全方面的原因,针对从用户模式的代码传给内核的任何实参,Windows都会把他们从线程的用户模式栈复制到线程的内核模式栈。一经复制,内核就可验证实参的值。由于应用程序代码不能访问内核模式栈,所以应用程序无法修改验证之后的实参值。OS内核代码将开始对复制的值进行处理。除此之外,内核会调用它自己内部的方法,并利用内核模式栈传递它自己的实参、存储函数的局部变量以及存储返回地址。在32位Windows上运行时,内核模式栈大小为12KB,在64位Windows上运行时,大小则为24KB

  • DLL线程连接(Attach)和线程分离(Detach)通知

Windows的一个策略是,任何时候在进程中创建一个线程,都会调用哪个进行中加载的所有DLL的DllMain方法,并向该方法传递一个DLL_THREAD_ATTACH标志。类似的,热河时候一个线程终止,都会调用进行中的所有DLL的DllMain方法,并向该方法传递一个DLL_THREAD_DETACH标志。有点DLL需要领用这些通知,为进程中创建、销毁的每个线程执行一些特殊的初始化或(资源)清理操作。例如,C-Runtime库DLL会分配一些线程本地存储状态。线程使用C-Runtime库中包含的函数时,需要用到这些状态。

PS:C#和其他大多数托管变成语言生成的DLL没有DllMain 函数。所以,托管DLL不会收到DLL_Thread_ATTACHDLL_THREAD_DETACH通知,这提升了性能。此外,非托管DLL可调用Win32 DisableThreadLibraryCalls函数来决定不理会这些通知。遗憾的是,许多非托管开发人员都不知道有这个函数。

 Windows 任何时刻只将一个线程分配给一个CPU。那个线程只能运行一个“时间片”(有时也称为“量”或者“量程”,即 quantum)的长度。时间片到期,Windows 就上下文切换到另一个线程。每次上下文切换都要求Windows 执行以下操作:

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

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

  现在,如果一个应用程序的线程进入无限循环,Windows会定期抢占(preempt)它,将一个不同的线程分配给一个实际的CPU,然后让这个新线程运行一会。假定新线程是任务管理器里的线程,现在终端用户就可以利用任务管理器来结束这个包含了无限循环线程的进程。之后,进程会终止,它处理的所有数据也会被销毁。但是,系统中的其他所有进程都继续运行,不会跌势它们的数据。当然,用户也不用重启。所以上下文切换通过牺牲性能来换取更好的用户体验。

 

 实际上,性能上的损耗比你想想中的还要厉害。是的,当Windows上下文切换到另一个线程的时候,会发生一定的性能损失。但是,CPU现在是要执行一个不同的线程,而之前的线程的代码和数据还在CPU的告诉缓存(cache)中,这使得CPU不必经常访问RAM(他的速度比CPU告诉缓存慢得多)。当windows上下文切换到一个新线程时,这个新线程极有可能要执行不同的代码并访问不同的数据,这些代码和数据不在CPU的告诉缓存中。因此,CPU必须访问RAM来填充它的告诉缓存,以回复高速执行状态。但是,在30ms后,一次新的上下文切换又发生了。

 

 执行上下文切换的时间取决于不同的CPU架构和速度。而填充CPU缓存所需的时间取决于系统中运行的应用程序、CPU缓存的大小以及其他各种因素。所以,我不可能给你一个关于上下文切换时间的确切值,甚至是估计值。唯一确定的是,如果要构建高性能的应用程序和组件,就应该尽可能的避免上下文切换。

!!!:

在一个时间片结束的时候,如果windows决定再次调度同一个线程(而不是切换到另一个线程),那么windows不会执行上下文切换。相反,线程将继续运行。这显著的改进了性能。设计自己的代码的时候,注意能避免上下文切换的就避免。

 

!!!:

一个线程可以自动的提前结束它的时间片。这是经常发生的,因为线程经常要等待I/O操作(键盘,鼠标,文件,网络等等)来完成。比如,“记事本”程序的线程经常处于重现状态,什么事情都不做:这个线程是在等待输入。如果用户按键盘上的J键,Windows会唤醒“记事本”线程,让它处理按键操作。“记事本”线程可能花费5ms来处理按键,然后调用一个win32函数,告诉Windows它准备好处理下一个输入事件。如果没有更多的输入事件,windows会让“记事本”线程进入等待状态(时间片剩余的部分就放弃了),使线程在任何CPU上都不再调度,知道发生下一次输入事件。这增强了系统的总体性能,因为正在等待I/O操作的完成的线程不会在CPU上调度,所以不会浪费CPU时间;节省出来的时间可供在CPU上调度其他线程。

 

 另外,执行GC的时候,CLR必须挂起所有的线程,遍历它们的栈来查找根以便对堆中的对象进行标记,再次遍历它们的栈(有的对象在压缩期间发生了移动,所以要更新它们的根),再恢复所有的线程。所以,减少线程的数量也会显著提升GC的性能。每次使用调试器并调试到一个断点,Windows都会挂起正在调试的应用程序中的所有线程,并在但不执行或者运行应用程序时恢复所有线程。因此,你用的线程越多,调试体验也就越差。

 

 根据上述讨论,我们的结论是必须尽可能的避免使用线程,因为它们要消耗大量内存而且它们需要很多时间去创建、销毁、管理。Windows在线程知己那进行上下文切换,以及在发生GC的时候也会浪费不少时间。然而,根据上述讨论,我们还得出了另一个结论,那就是有时必须使用线程,因为它们使windows更加健壮。

线程基础(CLR via C#)的更多相关文章

  1. 25线程基础-CLR

    由CLR via C#(第三版) ,摘抄记录... 1.线程是CPU的虚拟化,windows为每个进程提供专用线程(CPU)2.线程开销:内存和时间. 线程内核对象—OS为系统中创建的每个线程都分配并 ...

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

    趣闻:我是一个线程:http://kb.cnblogs.com/page/542462/ 进程与线程 进程:应用程序的一个实例使用的资源的集合.每个进程都被赋予了一个虚拟地址空间. 线程:对CPU进行 ...

  3. [CLR via C#]25. 线程基础

    一.Windows为什么要支持线程 Microsoft设计OS内核时,他们决定在一个进程(process)中运行应用程序的每个实例.进程不过是应用程序的一个实例要使用的资源的一个集合.每个进程都赋予了 ...

  4. 《CLR Via C#》读书笔记:26.线程基础

    一.线程开销 操作系统创建线程是有代价的,其主要开销在下面列举出来了. 内存开销 线程内核对象 拥有线程描述属性与线程上下文,线程上下文占用的内存空间为 x86 架构 占用 700 字节.x64 架构 ...

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

    第二十五章 线程基础 2014-06-28 25.1 Windows为什么要支持线程 25.2 线程开销 25.3 停止疯狂 25.6 CLR线程和Windows线程 25.7 使用专用线程执行异步的 ...

  6. CLR via C# 读书笔记-26.线程基础

    前言 这俩个月没怎么写文章做记录分享,一直在忙项目上线的事情,但是学习这件事情,停下来就感觉难受,clr线程这章也是反复看了好多遍,书读百遍其义自见,今天我们来聊下线程基础 1.进程是什么,以及线程起 ...

  7. C#多线程编程系列(二)- 线程基础

    目录 C#多线程编程系列(二)- 线程基础 1.1 简介 1.2 创建线程 1.3 暂停线程 1.4 线程等待 1.5 终止线程 1.6 检测线程状态 1.7 线程优先级 1.8 前台线程和后台线程 ...

  8. 【C#进阶系列】25 线程基础

    线程的概念 线程的职责是对CPU进行虚拟化. CPU为每个进程都提供了该进程专用的线程(功能相当于cpu),应用程序如果进入死循环,那么所处的进程会"冻结",但其他进程不会冻结,它 ...

  9. Qt之线程基础

    何为线程 线程与并行处理任务息息相关,就像进程一样.那么,线程与进程有什么区别呢?当你在电子表格上进行数据计算的时候,在相同的桌面上可能有一个播放器正在播放你最喜欢的歌曲.这是一个两个进程并行工作的例 ...

随机推荐

  1. Hibernate学习笔记一 使用idea开发工具搭建框架

    1.导包,包下载地址:http://hibernate.org/orm/downloads/ 2.创建数据库,准备表,实体.示例: CREATE TABLE `cst_customer` ( `cus ...

  2. 关于hadoop集群下Datanode和Namenode无法访问的解决方案

    HDFS架构 HDFS也是按照Master和Slave的结构,分namenode,secondarynamenode,datanode这几个角色. Namenode:是maseter节点,是大领导.管 ...

  3. Bate敏捷冲刺每日报告--day2

    1 团队介绍 团队组成: PM:齐爽爽(258) 小组成员:马帅(248),何健(267),蔡凯峰(285)  Git链接:https://github.com/WHUSE2017/C-team 2 ...

  4. 201421123042 《Java程序设计》第8周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains源代码 源代码: 答:查找 ...

  5. 第十二条:考虑实现Comparable接口

    与前面讨论的方法不同,compareTo()方法并没有在Object类中定义.相反,它是Comparable接口中唯一的方法. 一个类的实例对象要想是可以比较大小的,那么这个类需要实现Comparab ...

  6. 可空类型 Nullable<T>

    Nullable<T> 内部实现了显示和隐式转换 显示转换: public static explicit operator T(T? value) { return value.Valu ...

  7. react-native-image-picker 运用launchCamera直接调取摄像头的缺陷及修复

    在前几天用react-native进行android版本开发当中,用到了"react-native-image-picker"的插件:根据业务的需求:点击按钮-->直接调取摄 ...

  8. mqtt paho ssl java端代码

    参考链接:http://blog.csdn.net/lingshi210/article/details/52439050 mqtt 的ssl配置可以参阅 http://houjixin.blog.1 ...

  9. istio入门(04)istio的helloworld-部署构建

    参考链接: https://zhuanlan.zhihu.com/p/27512075 安装Istio目前仅支持Kubernetes,在部署Istio之前需要先部署好Kubernetes集群并配置好k ...

  10. Spring Security 入门(3-11)Spring Security 的登录密码验证过程 UsernamePasswordAuthenticationFilter

    认证过程如下 一.先判断请求(请求必须是post请求)地址是否为配置的 login-processing-url 值(默认/j_spring_security_check),如果不是,则放行,进入下一 ...