多线程编程(一)-- 揭开线程的神秘面纱

一、windows为什么要支持线程

  早期操作系统的状况:早期,操作系统没有线程的概念。整个系统只运行着一个执行线程,其中同时包含操作系统代码和应用程序代码。

  问题:长时间运行的任务会阻止其他任务的执行。某个应用程序的执行可能会冻结整个机器,造成OS和其他应用程序停止响应。如果应用程序有bug,会造成无线循环,同样会造成整个机器停止工作,并且会导致数据丢失。

  改进:构建一个新的OS来满足企业和个人的需要。

                 1、MS在设计这个OS的内核时。决定在一个进程中运行应用程序的每个实例进程是应用程序的一个实例要使用的资源的一个集合。每个进程都被赋予了一个虚拟的地址空间,确保一个进程使用的代码和数据无法由另一个进程访问。这就确保了应用程序实例的健壮性,因为一个进程无法破坏另一个进程使用的代码和数据。除此之外,OS的内核代码和数据是进程访问不到的,所以应用程序破坏不了操作系统的代码和数据。

                 2、但是CPU本身呢?如果一个应用程序进入无限循环,并且机器中只有一个CPU,那他会执行无限循环,不能执行其他任何东西。所以,系统还是可能会停止响应。MS对于这个问题拿出的解决方案是线程线程的职责是对CPU进行虚拟化。Windows为每个进程都提供了该进程专用的线程(功能相当于一个CPU,可将线程理解为一个逻辑CPU)。如果应用程序的代码进入无线循环,与那个代码关联的进程会“冻结”,但其他的进程还会继续执行。

 

二、线程开销

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

  每个线程中,都有以下要素:

  1、线程内核对象。

  2、线程环境块。

  3、用户模式栈。

  4、内核模式栈。

  5、DLL线程链接和线程分离通知。

  创建和终止线程引起的开销:任何时候在进程中创建一个线程,都会调用那个进程中加载的所有DLL的DllMain方法,并向该方法传递一个DLL_THREAD_ATTACH标识。类似的,任何时候一个线程终止,都会调用进程中的所有DLL的DllMain方法,并向该方法传递一个DLL_THREAD_DETACH标识。(注:C#和其他大多数的编程语言生产的DLL没有DLLMain函数,所以托管DLL不会收到DLL_THREAD_ATTACH和DLL_THREAD_DETACH的通知,这提升了性能。)

  上下文切换引起的开销:单CPU的计算机一次只能做一件事情。所以Windows必须在系统中的所有线程(逻辑CPU)之间共享物理CPU。在任何给定的时刻,Windows只将一个线程分配给一个CPU,那个线程允许运行一个“时间片”,一旦时间片到期,Windows就上下文切换到另一个线程,每次上下文切换都要求Windows执行以下操作:

  1、将CPU寄存器中的值保存到当前正在运行的线程的内核对象内部的一个上下文结构中。

  2、从现有的线程集合中选出一个线程供调度。如果该线程有由另一个进程拥有,Windows在开始执行任何代码或者接触任何数据之前,还必须切换CPU“看见”的虚拟地址空间。

  3、将所选上下文结构中的值加载到CPU的寄存器中。

  Windows大约每30毫秒执行一次上下文切换。上下文切换是净开销:也就是说上下文切换所产生的开销不会换来任何内存或者性能上的收益。Windows执行上下文切换,向用户提供一个健壮的、响应灵敏的操作系统。执行上下文切换所需要的时间取决于CPU架构和速度。而填充CPU缓存所需时间取决于系统中运行的应用程序、CPU缓存的大小及其他各种因素。所以无法为每一次上下文切换的时间开销给出一个确定的值,甚至无法给出一个估计的值。唯一确定的是,如果要构建高性能的应用程序和组件,就应该尽可能的避免上下文切换。

  在执行垃圾回收时,CLR必须挂起(暂停)所有线程,遍历他们的栈来查找跟以便对对堆中的对象进行标记,再次遍历他们的栈(对象在压缩期间可能发生了移动,所以要更新他们的根),再恢复所有线程。所以,减少线程的数量会限制提升垃圾回收器的性能。

  未完待续.........

  

posted @ 2017-10-09 09:54  苏苏喂苏苏+  阅读(627)  评论(0编辑  收藏  举报