一.为什么需要优先级--线程调度的问题
在现实生活中,优先级是一个很常见的现象:在火车站,如果你是孕妇,你是可以走进站中的专门绿色通道的,可以提前上火车以免拥挤;火警119匪警110出警的时候,都是人命关天的大事,是可以优先使用道路的,行人和车辆一律避让;在银行,如果你是白金VIP,也会有专门的绿色通道供你使用.从社会公平层面的优先级,到商业活动中的优先级,大家每天都生活在这样的环境之中,而且是按照这套游戏规则行事。
Windows是一个用户操作系统,在设计上遵循着稳定性为准则,也就是说他要与人类的思维方式保持一致,才能用得舒服,线程的优先级可以说就是这一原则下的产物.如果都让车辆在路上一直依次行驶,那么120到现场的时候,人都可能已经挂了;如果病毒一直抢占CPU,那杀毒软件也就没有什么卵用了。
这里需要注意现实生活中优先级与Windows中优先级的不同之处,现实生活中,我们可以通过新增资源(如增加窗口)优先处理高级别的业务,也可以通过对共同资源的调度(如110出警)来处理优化优先级高的业务。Windows则是在借鉴这些思路的时候进行了自己的一些改造,如调度队列、动态优先级调整等。
二.了解Windows的线程调度
Windows进化到现在,已经是一个时分和抢占式操作系统,它不同于实时操作系统,它通过动态地调整线程等待队列,从而确定线程使用cpu时间片的顺序。优先级高的线程有更大的概率排在队列的前面,同时获得更多数量的时间片,而不是更长时间的时间片。
线程如果被sleep掉、等待io时,会释放他所拥有的cpu,如果线程不是主动释放CPU,线程调度器会抢占该线程。如果优先级相同的多个线程等待使用CPU,则会使用一个循环调度规则来实现队列的先后顺序。
前面我们说过优先级是动态调整的,优先级低的不可能一直都是在等待,随着时间的运转,低优先级线程的优先级会提升,这样线程才有可能在等待结束时获得CPU。
再次强调,线程优先级只是提高了线程被调用的概率,并不是定义CPU调用线程的顺序,具体的工作就交给操作系统内部了。
三.C#中的线程优先级
C#中为我们提供了5个线程优先级别,如下所示:
线程默认的优先级为Normal,可以在线程的运行过程中修改线程的优先级,如下所示:
static void Main(string[] args)
{
Thread th = new Thread(delegate() { Console.WriteLine("start a new thread"); });
Console.WriteLine(th.Priority);
th.Start();
th.Priority = ThreadPriority.Lowest;
Console.WriteLine(th.Priority);
Console.Read();
}
结果如下:
四.优先级反转
这里要用到锁的知识,对这块有疑问的可以先去大致了解下锁。
假设一种情况,线程A是高优先级线程,线程B是低优先级线程,线程B等啊等,终于等到自己,然后它锁住了一个资源R,然后线程A接手,但是它要用到R,R被锁,然后A只有等待B释放锁,然而B优先级低,它只有经过漫长的等待才能提高自己的优先级得到执行,而此时A仍然在等待B释放锁,从表而看线程A是有非常多的执行机会,但线程A并未执行任何代码,反而B一直在执行,那么线程A何时能够得到资源呢?不知道,原来的优先级设定也就失去了意义。这就是所谓的优先级反转。
那么,如何避免这一问题呢?这里给出一些建议。
1.锁的尺寸应该尽量小,就是使用小锁而非大锁,比如锁定字符串就是一个非常大的锁
2.锁的代码应该尽量短,这样锁定的时间就会尽量少
3.可以使用原子锁
使用优先级继承:也就是,高优先级进程TH在等待低优先级的线程TL占用的竞争资源时,为了使TH能够尽快获得调度运行,由操作系统把TL的优先级提高到TH的优先级,从而让TL以TH的优先级参与调度,尽快让TL执行并释放掉TH欲获得的竞争资源,然后TL的优先级调整到继承前的水平,此时TH可获得竞争资源而继承执行。优先级继承还可以存在传递性,也就是说TL还需要更低的低优先级线程TM的同步资源时,也会把TM的线程优先级拉高到TL,这要看各个操作系统的具体实现。
五.结尾
在做应用程序时,我们建议,尽量不去干预线程的优先级,把调度的顺序交给操作系统处理就好。
六.参考
《C#高级编程(第7版)》