zoukankan      html  css  js  c++  java
  • CLR_via_C#.3rd 翻译[25.9 线程调度和优先权]

    25.9 Thread Scheduling and Priorities 线程调度和优先权

     

    抢占式(preemptive)操作系统必须使用某种算法,来决定在什么时候调度哪些线程以及调度多长时间。本节将讨论Windows采用的算法。本章前面部分,我提到每个线程的内核对象都包含一个上下文结构。上下文结构反映了当线程上一次执行时,线程的CPU寄存器的状态。在一个时间片(time-slice)之后,Windows检查现有的所有线程内核对象。在这些对象中,只有那些没有正在等待什么的线程才适合调度。Windows选择一个可调度的线程内核对象,并上下文切换到它。Windows实际记录了每个线程被上下文切换到的次数。可以使用像Microsoft Spy++这样的工具查看这个数据。图25-5展示了一个线程的属性。注意,这个线程已被调度了103505次。

     

    在这个时候,线程正在执行代码,并且在它的地址空间操作数据。过了另一个时间片后,windows 执行另一个上下文切换。Windows 从系统启动的时候就开始执行上下文切换,直到系统关闭的时候为止。

     

    之所以windows 被叫做抢占式多线程操作系统,是因为任何时候线程可能停止并调度另一个线程。就像你接下来看到的那样,你有控制权,但不是全部的。记住,你不能保证你的线程永远运行。

     

    Note开发者经常会问我,如何才能保证线程在某些事件之后一段时间内开始运行——比如,如何确保某个线程在串口接收到数据的1ms 内开始运行。我的回答:你不能。

    实时操作系统能做这样的保证,但windows 不是实时操作系统。实时操作系统需要对硬件(硬盘控制器、键盘以及其他组件的延迟时间)的运行情况有一个精确的把握。但是,Microsoft Windows 的设计目标是兼容大范围的硬件,包括不同的CPU、不同的驱动、不同的网络等等。简单地说,Windows 不是设计成实时操作系统。我还要补充一句:CLR使托管代码的行为变得更不“实时”了。这是有很多原因的,包括DLL的JIT加载,代码的JIT编译,以及GC介入的不确定性。

     

    每个线程都被赋予了0(最低)到31(最高)级别的权限。当系统需要分配线程到CPU的时候,它会检查权限级别是31的线程,并以轮流(round-robin)的形式被调度。如果31 级优先权的线程是可调度的,那么它将被分配到CPU。在这个线程的“时间片”的最后,系统会检查是否还有其他级别为31 并且能运行的线程;如果是就被分配到CPU

     

    只要31优先级的线程是可调度的,那么就轮不到优先级为030 的线程被调度到CPU。这种条件叫做“饥饿starvation”,当较多较高权限的线程占用了CPU,导致较低权限的CPU无法执行,就会发生这种情况。在多处理器机器上饥饿发生的可能性要小的多,因为这种机器上优先级为31的线程和优先级为30的线程可以同时运行。系统总是保持每个CPU处于忙碌状态,只有没有线程可调度的时候,CPU才空闲下来。

     

    较高优先级的线程总是抢占较低优先级的线程。例如,如果有一个优先级为5的线程在运行,同事又有一个更高优先级的线程准备要运行,系统会马上挂起较低权限的线程(及时它正运行到它时间片的一般)并且分配CPU给较高的线程,而且该线程获得“满时间片”。

     

    当系统启动的时候,系统会创建一个叫做“0页线程 zero page thread”的特殊线程。这个线程是整个系统唯一获得0级优先权的线程。页线程负责在其他线程要执行的时候,将RAM中的自由页归零。

     

    微软意识到,开发人员在为线程分派优先级的时候,很难做到完全合理。为此,windows公开了优先级系统的一个抽象层。

     

    设计应用程序的时候,应该决定自己顶应用程序是需要比机器上同时运行的其他应用程序更大还是更小的响应能力。然后选择一个进程优先级类(priority class)来反映你的决定。Windows支持6进程优先级类:IdleBelow NormalNormalAbove NormalHighRealtime 。因为Normal是默认优先级类,所以它是最常用的优先级类。

     

    如果一个应用程序(比如屏幕保护程序)在系统什么事情都不做的时候运行,就适合分配Idle优先级类。注意,即使一台计算机没有被交互的使用,仍有可能处于忙的状态(比如作为一台文件服务器运行),它不应该和一个屏幕保护程序竞争CPU时间。一些执行统计学跟踪分析的应用程序需要定期更新与系统有关的状态,这种应用程序一般不应妨碍执行更关键的任务。

     

    只有在非常重要的时候,你才能使用High priority类。如果可能,尽量避免使用real-time类。Realtime 的优先级非常高,它甚至可能敢要操作系统任务,比如阻碍一些必要的磁盘I/O和网络传输。除此之外,一个Realtime进程的线程可能造成不能及时的处理键盘和鼠标输入,用户户觉得自己的计算机“死机”了。简单的说,必须要有很好的理由才能使用Realtime优先级,比如需要响应延迟(latency)很短的硬件事件,或者要执行一些不能中断的非常“短命”的任务。 

     

    Note为了保证系统能够流畅运行。进程使不能以Realtime的优先级来运行的,除非用户有“提高调度优先级(Increase Scheduling Priority)”特权,否则进程不能以Realtime 优先级类运行。任何用户只要是管理员或者Power User,就默认有这个特权。

     

    一旦你选择了一个优先级类,不暂时不用考虑你的应用程序和其他应用程序的关系。只要把注意力放到你的应用程序的线程上。Windows 支持7相对线程优先级relative thread priority):IdleLowestBelow NormalNormalAbove NormalHighest 以及 Time-Critical。这些优先级是相对进程优先级的。同样的,默认的相对线程优先级是Normal

     

    总而言之,你的进程是一个优先级类的成员。在你的进程里,要为各个线程分配相对优先级。到目前为止,我一直没有提到关于031的线程优先级的事情。应用程序开发者永远不会直接处理这些优先级。相反,系统将进程的优先级类和其中的一个线程的相对优先级映射到一个优先级(031)。表25-1总结了进程的优先级类和线程的相对优先级的映射关系。

     

     

     

    举个例子,Normal 进程中的一个Normal 线程的优先级是8.由于大多数进程都是Normal 优先级,大多数线程也是Normal 优先级,所以系统中的大多数线程的优先级都是8.

     

    对于High 优先级进程中的一个Normal 线程,它的优先级是13.如果将进程的优先级类改成Idle,线程的优先级变成4。记住,线程的优先级是相对于进程优先级类的。如果更改以进程的优先级类,线程的相对优先级不会改变,但它的优先级值会改变。

     

    请注意,表中线程优先级没有0。这是因为前面我们已经讲到的页线程,0优先级保留给0页线程了,系统不允许其他线程的优先级为0.而且一下优先级也是不能获得的:1718192021272829 以及30。但是,如果是编写运行在内核模式的设备驱动程序,可以获得这些优先级;用户模式的应用程序是不能获得的。还要注意,Realtime 优先级的线程,其优先级不能低于16。同理,非Realtime 优先级小吃店优先级不能高于15

     

    Note大家可能对“进程优先级类”这个概念比较疑惑。你可能会觉得这就意味着windows能调度进程。但是,windows是永远不会调度进程的;它调度的只有线程。“进程优先级类”是Microsoft 提出的一个抽象的概念,是为了帮助你理解自己的应用程序和其他正在运行的应用程序的关系,它没有别的用途。

     

    Important:一般情况下,建议降低一个线程的优先级,而不是提高另一个线程的优先级。如果你要运行一个需要长时间的计算限制的任务(比如编译代码、检查拼写、重算电子表格等等),一般建议降低该线程的优先级。如果线程要快速下响应某个事件,然后运行非常短暂的时间,再回复为等待状态,则建议提高该线程的优先级。高优先级线程在其声明中的大多数时间里都处于等待状态,这样才不至于影响系统的总体响应能力。Windows 资源管理器(Windows Explorer)通过按键盘上的win 键来响应就是一个例子。当用户按下win 键,windows 资源管理器会抢占其他较低优先级的线程,并列出菜单。用户在菜单上选择的时候,windows 资源管理器的线程会快速响应每一次按键(或者是鼠标移动),更新菜单,并停止运行,知道用户继续在菜单中导航。

     

    正常情况下,进程根据启动它的进程来分配到一个优先级(Normally, a process is assigned a priority class based on the process that starts it running)。大多数进程都是由windows 资源管理器启动的,它在Normal 优先级类中生成它的所有子进程。托管应用程序不应该表现为拥有它们自己的进程;相反,它们应该表现为在一个AppDomaiin 中运行。所以,托管应用程序不应该更改它们的进程的优先级类,因为这会影响进程中运行的所有代码。例如,许多ASP.NET 应用程序都在单个进程中运行,每个应用程序都在它自己的AppDomain 中。类似的还有Silverlight 应用程序,它在一个Internet 浏览器进程中运行。还有托管存储过程,它在Microsoft SQL Server 进程中运行。

     

    另一方面,你的应用程序可以更改它的线程的相对线程优先级,这需要设置Thread 的Priority 属性,向它传递ThreadPriority 枚举类型中定义的5个值之一,即Lowest,BelowNormal,Normal,AboveNormal 或者Highest。然而,就像Windows 为自己保留了优先级0和Realtime 范围一样,CLR为自己保留了Idle和Time-Critical优先级。今天的CLR还没有以Idle优先级运行的线程,但这一点将来可能发生改变。然而,如第21章“自动内存管理(垃圾回收)”讨论的那样,CLR的终结器线程以Time-Critical 优先级运行。所以,作为托管应用程序的开发人员,你实际只需使用表25-1中5个加了底纹的相对线程优先级。

     

    Important:如今,大多数应用程序都没有利用线程优先级。然而,在我设想的世界中,CPU 保持100%的使用率。在这种情况下,为了保证系统响应能力不受影响,线程优先级就显得至关重要。遗憾的是,多年来最终用户已养成了一个习惯:一旦看到太高的CPU 使用率,就甘距应用程序要失去控制了。在问的新世界中,需要对用户进行“知识普及”,让它们明白高的CPU使用率是一件好事情——表明计算机正在主动为用户处理有用的信息。如果所有CPU都忙于优先级8和以上的线程,就真的出问题了。这意味着应用程序在响应用户的输入时遇到麻烦。可能未来的“任务管理器”在报告CPU 使用情况时,会将线程优先级考虑在内;诊断有问题的系统时,这种信息是相当有帮助的。

     

    应该指出的是,System.Diagnostics命名空间包含一个Process类和一个ProcessThread类。这两个类分别提供了进程和线程的Windows视图。如果要开发者用托管代码写工具(utility)应用程序,或者想建构代码来帮助自己对其进行调试,就可以使用这两个类。事实上,这正是为什么这两个类在System.Diagnostics命名空间中的原因。应用程序需要以特殊的安全权限运行才能使用这两个类。例如,在Silverlight应用程序或者ASP.NET应用程序中,就不能使用这两个类。

     

    另一方面,应用程序可使用AppDomain和Thread类,它们公开了AppDomain和线程的CLR视图。大多数情况下,不需要特殊安全权限来使用这两个类,虽然某些操作仍需提升权限才可以。

  • 相关阅读:
    IServiceBehavior, IOperationBehavior,IParameterInspector
    System.IO.Pipelines——高性能IO(三)
    System.IO.Pipelines——高性能IO(二)
    System.IO.Pipelines——高性能IO(一)
    背包问题 —— 四种解法解题
    波音,自动驾驶bug未修复,致346人丧生!5个月内两次坠毁!其中,包括8名中国公民
    2018年Java生态行业报告
    为什么大公司一定要使用DevOps?
    设计微服务的最佳实践
    Spring Boot面试题
  • 原文地址:https://www.cnblogs.com/TivonStone/p/1824518.html
Copyright © 2011-2022 走看看