一个抢占式操作系统(比如Microsoft Windows)必须使用某种算法来决定那个线程应该被调度,应该调度多长时间。
每个线程的线程内核对象都维护了一个CONTEXT上下文结构,里面存放了线程最近一次被CPU执行的寄存器信息和状态。大约每20毫秒,Windows查看当前所有存在的线程内核对象,在这些对象中,只有一些是可以调度的。然后Windows选择其中的一个可以调度的线程内核对象并加载保存在这个内核对象中的CONTEXT结构中的寄存器信息,这个过程称为“contest switch”。Windows实际上也保存了一个记录,记录着每个线程被执行的次数。
然后,线程开始执行代码和处理数据,这些代码和数据存在该线程所在的进程的进程地址空间中。大约20毫秒之后,Windows将CPU的寄存器信息保存到该线程的CONTEXT结构中,同时该线程暂停运行。系统再次查看所有可以调度的线程内核对象,然后选择另外一个线程内核对象,并加载其CONTEXT结构,然后继续,如此往复……
注意,无法得到线程运行的确切时间。因为这个是实时操作系统的功能,而Windows是抢占式操作系统。
在系统中,很多线程是处于“挂起(Suspended)”状态的,因为它们需要等待一个或多个特定的事件发生之后,方可由挂起状态变为可调度状态。
在线程内核对象的内部有一个“值”指明了当前线程的“挂起计数”。当调用CreateProcess和CreateThread函数创建线程的时候,线程内核对象被创建,然后“挂起计数”被初始化为1,以此防止刚创建的线程被调度,因为可能初始化工作还没有完全做好。当线程被完全地初始化之后,CreateProcess和CreateThread函数查看你是否传递了CREATE_SUSPENDED旗标(flag)。如果传递了这个旗标,那么函数返回,新的线程进入“挂起”状态。如果没有传递该旗标,那么函数递减“挂起计数”为0。当一个线程的“挂起计数”为0,表明它是可以调度的(除非等待在某些事件发生或其他对象上,比如键盘输入)。
创建一个“挂起状态”的线程,意味着你可以在线程执行之前更改该线程的环境(比如优先级)。你可以使用ResumeThread函数来使得这个线程变为可调度状态。
如果该函数成功,返回线程的前一个挂起计数,否则,返回-1(0xFFFFFFFF)。
一个线程可以被挂起多次,如果一个线程被挂起3次,那么需要呼叫ResumeThread函数来唤醒它3次,ResumeThread会“递减”指定线程的“挂起计数”。
挂起一个线程,只要呼叫SuspendThread函数,传递一个线程句柄给它就可以了。
一个线程可以呼叫这个函数从而挂起另一个线程。该函数也返回线程的前一个“挂起计数”。需要注意的是,SuspendThread函数对于内核模式来说是异步的,当线程挂起之后,不会发生用户模式的执行。
想要让一个线程“睡眠”一段时间,可以呼叫Sleep函数。
运行该函数后,在一个指定的时间之内,线程不会再被调度。你可以传递INFINITE该这个函数,让线程无休止地休眠下去。也可以传递一个普通的正数,让线程休眠一定的时间(以毫秒为单位)。也可以传递0,这说明该线程放弃当前的时间片,强制系统调度另一个线程。但是系统可以随时调度这个线程,也就是说这个线程不休眠。
系统提供了一个函数,允许转而调度另一个线程。该函数是:
呼叫该函数使得系统查看当前是否有“饥饿”的线程,如果有,该函数立即调度饥饿线程,可能这个线程具有比较低的优先级。
当存在可以被调度的线程的时候,该函数返回TURE,否则返回FALSE。
该函数与调用Sleep(0)的区别是,该函数允许低优先级的线程被调度。