C#线程篇---Windows调度线程准则(3)

Windows本身就是一个抢占式操作系统,它的实现,必定有某种算法在里面,比如什么时候调度哪些线程,需要花费多长时间等问题。

我们时时在用Windows,作为程序员,我们有必要知道其中最贴近我们的算法。

为什么这么说?我们对系统发出的命令,获取信息等操作,Windows为什么能这么快作出反应吗?这仅仅是上下文切换那30毫秒的功劳吗?操作系统能依照人的操作,处理当前用户最迫切的请求,并在最短时间内给出反应,这些原因我们应该知道。

  有人会提,这是线程的功劳,对。这是线程的功劳,你在操作的时候,都是线程在处理你的请求,现在来了解下线程的属性吧?

  打开开始菜单,win7直接在搜索框中输入spy,然后会弹出一个叫spy++的程序,运行spy++。这个东西是VS2010完整版才有。

这是个有趣的东西,好奇的同学可以试试看。现在来看看这个东西能做什么吧。

我找到了一个QQ的线程的窗口,然后右键,弹出菜单,然后选择->消息。

出来一个框,数据一直再刷,然后再呼出QQ界面看?鼠标在QQ上面晃两下,细心的你会发现。刷屏的消息是不是很有感觉?再心细的又会观察一下属性选项,进程ID你也会看到,一个应用程序也就一个进程,关系QQ的线程,所有的进程ID都会是一样。在你每次操作的时候,Windows做着超乎你想象的工作,试一试,也许也会让你惊呼。消息看完了,来监视下线程吧?在spy++中选择 监视->线程

我找到的是QQ的线程:

上下文开关就是记录Windows上下文切换次数,”2453261“这是系统已经调用QQ线程的次数。而你看到的,就是一个线程的属性。

  现在你也许又会有疑问了,为什么会这样?

  在前两篇同系列文章中,提到过每个线程都有自己的属性,在每个线程的内核对象之中,都包含一个上下文结构,上下文结构的存在是为了反映在线程上一次执行时,线程CUP寄存器的状态。在任何时刻,Windows只将一个线程代码分配给一个CPU,一个线程只允许运行一个时间片,在线程的“时间片”结束之后,Windows会检查现有所有线程内核对象,只有那些没有在等待什么的线程才时候调度。Windows选择一个可调度的线程内核对象,并且换到它。

  Windows选择一个可调度的线程有一套独特的标准,看到上图中的线程的优先级了吗?当前优先级和基本优先级,它是随着程序的启用与否波动的。

  一个线程允许运行一个时间片,没错。但是,Windows执行线程的规律和时间片没多大的关系,线程在运行的任何时刻都可以停止,然后Windows又去调度另一个线程,你有点控制权,去控制你想运行的线程,但是这控制权不多,不控制为好。

对于线程的执行,记住一点:

 你不能保证自己的线程一直运行,你不能阻止其他的线程的运行。

因为Windows不是一个实时操作系统,想了解更多就请自行去深挖吧,我也不多写了,免得扯出范围(─.─|||。

  现在把目光放到线程优先级上,每个想成为优秀的程序员,必须要了解的知识点。

  线程优先级别0~31,Windows把线程用从高到低的调度方式轮流调度线程,假如有一个优先级别为31的线程运行结束了,然后Windows会找下一个空闲的线程,如果空闲的线程中有一个级别也是31的线程,那么Windows又会把31级别的线程交给CUP处理。

  那就有人要问了,如果一直有31的线程级别,那岂不是低级别的线程Windows都不会去调用了?这不公平。

  确实,Windows就是个这样的不公平。有高级别线程,低级别线程没有机会去请求使用CUP。

  CPU正在运行一个低级别的线程,当一个优先级别高于正在运行的低级别的线程,级别高的线程请求调用CPU,那么,会怎么样?

  Windows会毫不犹豫的把正在运行的低级别线程赶走,然后放高级别的线程去使用CPU,就是有这么霸道。

  较高优先级线程总是抢占较低级别线程,这有没有弱肉强食的感觉?

好,说完级别调用的问题,就来看看,怎么设计应用程序的运行线程级别吧。

  在设计应用程序时,应觉得自己的应用程序是需要比机器上同时运行的其他应用程序更大还是更小的响应能力,然后选择一个进程优先级类(注意)。

  为什么要引进进程优先级类?

  Microsoft其实已经认识到,开发人员在为进程优先级分配时,很难做到完全合理,你不知道这个进程的优先级应该设为多少,为了解决这个分配优先级问题,Windows公开了优先级类的一个抽象层。以此来反应你的决定,Windows支持6个进程优先级类:Idel,Below Normal,Normal,Above Normal,Hight和Realtime(依次向高),其中Normal是默认的进程优先级,所以它是最常用的。

  如果一个应用程序在系统什么都不做的情况下运行,就适合分配一个Idel的优先级类。估计QQ的离开状态,屏保等是这么设计的,当然还有很多....

  进程划级时,要考虑最关键的因素,不能让进程妨碍其它更关键的任务,只有在绝对必要的时候才使用Hight优先级类,Realtime最高优先级一般得避免,因为它相当高,到31的级别了,它甚至可能干扰操作系统任务,比如阻碍一些网络传输,磁盘读写等,你肯能觉得没什么,除此之外,Realtime进程的线程可能造成不能及时的处理键盘和鼠标输入,用户觉得自己的计算机”崩了(死机)“,一定要有很好的理由才能使用Realtime优先级,比如响应一些延迟很短的硬件事件。

  选好一个进程优先级类之后,你的程序和其他应用程序就不用再考虑了,现在把专注力放在应用程序的线程上:

  Windows支持7个相对线程优先级:Idel,Lowest,Below Normal,Normal,Above Normal,Highest和Time-Critical。

  这些是相对于进程优先级的,Normal依旧是默认的,它是最常用的,现在来打个比方,用个商场来做例子:

这图勉强入眼。

每个服饰品牌都不一样,代表着进程优先级不一样。

每个品牌下的服饰的价格不一样,代表着它们线程优先级不一样。

比如阿迪达斯,耐克等,它们进程优先级高,里面相对应的线程优先级也高。因为它们价格比一般的品牌要贵。

品牌的高低决定服装的价格,进程优先级的高低觉得相对线程优先级程度!经过这一举例,下面这个表应该看得懂了:

相对线程优先级

进程优先级

Idle

Below Normal

Normal

Above Normal

High

Realtime

Time-critical

15

15

15

15

15

31

Highest

6

8

10

12

15

26

Above Normal

5

7

9

11

14

25

Normal

4

6

8

10

13

24

Below Normal

3

5

7

9

12

23

Lowest

2

4

6

8

11

22

Idle

1

1

1

1

1

16

  记住:

如果更改一个进程的优先级类,线程的相对优先级不会改变,但它的优先值会改变

  也许大家对这些表中数字会有疑惑,这代表的是先前说的0~31线程优先级别,但为什么这个表里面没有“0”这个级别?

  “0”这个级别是有的,不过它保留给零页线程了,什么是零页线程?系统在启动时,会创建一个名为零页线程的特殊线程,它是整个系统唯一线程级别为0的线程,它辅负责在没有其他进程需要执行的时候,将系统的RAM所有空闲页清零。表中还有一些数字没有出现:17,18,19,20,21,27,28,29,30。这些线程级别不能被用户模式获得,在编写运行于内核模式的设备驱动程序时,才可以获得这些优先级。

  还要注意的是,Realtime优先级的线程,其优先级不能低于16,类似的,非Realtime优先级的线程不能高于15。

进程优先级类和相对线程优先级有没有分清楚呢?这个概念容易引起混淆,大家可能认为Windows能调度进程,然而,Windows永远都不会调度进程,他调度的只有线程,“进程优先级类”是Microsoft提出的一个抽象概念,目的是为了帮助你理解自己的应用程序和其他正在运行的应用程序的关系,他没有别的用途。

  来看个设计实例:

  现在有一个线程要设计,他用于长时间运行的计算限制任务,比如:编译代码,拼音检查,电子表格计算等计算功能。一般是降低这线程的优先级,而不是提升线程的优先级。如果要设计一个快速的响应时间,然后运行非常短暂的时间,再回复等待状态,则应提高该线程的优先级,这里总结的规律是,高优先级线程在其生命中的大多数时间里,都应处于等待状态,这样才不会影响熊的总体响应能力。

  现在请按一下键盘右下角位置的Windows菜单键,看到效果了?Windows菜单立即抢占其他低级线程,并显示它的菜单,用户在菜单中上下移动时,菜单的线程会快速响应每一次按键或者鼠标移动。这是个很好的高优先级线程的例子,它在用户关闭菜单后停止运行。

  你可以更改它的线程相对优先级,Thread中的Priority属性,向它传递ThreadPriority枚举类型中定义的5各值之一,即在上表中的灰色部分列。

  Windows为自己保留了优先级0和Realtime范围,CLR为自己保留了Idle 和Time-Critical优先级。

  CLR的终结器线程以Time-Critical优先级运行。开发人员不用用到这些优先级,但了解一下还是不错的。

优先级的存在,使得应用程序需可以更人性化的处理用户的请求,这设计的相当不错,没有它,我们不能随意操控命令机器。

线程基础只是讲完了,作为开发人员应该知道,线程是非常宝贵的资源,必须省着用,为了做到这一点,最好的方式就是使用线程池ThreadPool。

这个下篇讲,不得不说,线程池自动管理线程的创建和销毁,这非常不错。(^。^)y-~~

原文发布于微信公众号 - 我为Net狂(dotNetCrazy)

原文发表时间:2016-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT技术精选文摘

解Bug之路-记一次JVM堆外内存泄露Bug的查找

15130
来自专栏IMWeb前端团队

再见2015 再见cmd

本文作者:IMWeb yisbug 原文出处:IMWeb社区 未经同意,禁止转载 2015年已经快要过去了,你是否还在使用有着十几年历史的cmd命令行...

36190
来自专栏FreeBuf

Web黑盒渗透思路之猜想

场景:WEB后台爆破 后台爆破很多人都会选择最经典的模式,如字典爆破,挖掘未授权访问漏洞,挖掘验证码漏洞(未刷新,验证码识别)等方法。 猜想: 1、后台程序是采...

27450
来自专栏工科狗和生物喵

Mac OS X 下非官方软件自启动处理

正文之前 说是处理,想必也没几个人喜欢自启动的软件,我是一个控制欲比较强的人,开机自启搜狗输入法这个我能接受,但是像印象笔记啊,向日葵圆孔Client客户端这些...

69760
来自专栏解Bug之路

解Bug之路-记一次JVM堆外内存泄露Bug的查找 顶

JVM的堆外内存泄露的定位一直是个比较棘手的问题。此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。笔...

10740
来自专栏Albert陈凯

常见编程语言对REPL支持情况小结

最近跟一个朋友聊起编程语言的一些特性,他有个言论让我略有所思:“不能REPL的都是渣”。当然这个观点有点偏激,但我们可以探究一下,我们常用的编程语言里面,哪些支...

36340
来自专栏FreeBuf

Kali Linux渗透基础知识整理(四):维持访问

*本文原创作者:sysorem 维持访问 在获得了目标系统的访问权之后,攻击者需要进一步维持这一访问权限。使用木马程序、后门程序和rootkit来达到这一目的。...

41880
来自专栏编程之旅

Liunx的文件权限

之前讲过为了统一开发环境生产环境以及更换开发机器的情况,我把环境统一由Vagrant部署在Linux的虚拟机中,但是由于我对Linux系统没有系统的学习过,对于...

12110
来自专栏月色的自留地

在龙芯小本上安装Debain8.10

26260
来自专栏月色的自留地

在龙芯小本上安装Debain8.10

26240

扫码关注云+社区

领取腾讯云代金券