荷兰科学家设计的信号量算法为线程使用共享资源提供了有效的同步和互斥机制,NT内核中,信号量(KSEMAPHORE)通过封装DISPATCHER_HEADER结构实现计数器和等待队列,其数据结构struct _KSEMA-PHORE{DISPATCHER_HEADER Header LONG Limit}在参考文献[3]中有详细描述,上述结构可简略为:
(1)后进先出LIFO(Last In First Out),线程请求资源后,若信号量计数器小于0,将线程排在线程等待队列的队头。该策略易于实现,线程等待队列只需一个单链表即可,这种“后来先服务”的方式还可以利用CPU缓存TLB(Tanslation Lookaside Buffer)中存在的刚被挂起线程的页表数据,不必更新缓存,提高了运行速度。但是,后进先出方式让最先被挂起的线程鲜有被服务,若获得资源的线程高频率请求资源,会导致最先请求资源的线程由于长时间处在队尾得不到服务导致“饿死”(Starva-tion),使得一些线程频繁调度,而一些线程很少被调度。
(2)先进先出FIFO(First In First Out),线程请求资源后,若信号量计数器小于0,将线程排在线程等待队列的队尾。该策略克服了线程的“饿死”现象,对资源有请求的线程都能公平地占有资源,请求队列调度均衡化,从策略角度来看,所有线程都整齐划一无差别。这种先来先服务的方式没有考虑线程的其他因素,例如,对时间紧要程度的要求不同,有实时线程和一般线程之分,如果对实时线程和一般线程都采用先进先出方式,那么实时线程的实时性将大幅降低,特别在等待队列中已有很多线程的情况下,实时线程只有等待前面所有线程释放信号量后才能得到调度,造成不必要的延时。信号量的数据结构和操作也要复杂一些,需要一个队尾指针。
从表1可以看出:1~12行的调度结果和前述分析的各种策略的优缺点一致,对于FIFO,无论不同优先级线程的比例是多少,它们被调度的次数几乎完全相同。对于LIFO,从数据可以看出,两个优先级为8、一个优先级为6和优先级为26、25、24的线程处在等待队列的前端,而且几乎每次都是这几个线程被调度。对于Priority,无论是否有实时类线程,只要优先级高,被调度的次数就多。对于新策略(Priority and FIFO),有实时线程就按优先级调度,若只有一般线程就按照FIFO调度,既有FIFO的特性(比较第2行和第11行)也有Priority的特性(比较第1行和第4行),而其他策略则只具有一种特性。应用程序在其他操作系统测试结果见14~22行,比较可以看出,14~22行的数据与10~12行的数据几乎完全一致,由此可以推断Windows 7操作系统的信号量等待队列也是先进先出策略。