线程的执行总是趋向于CPU受限或IO受限
一些线程需要花费一定的时间使用CPU进行计算,而另外一些线程则会花费一些时间等待相对较慢的I/O操作的完成
一个用于计算16位整数的14次方根的线程属于前者,而一个等待人类用户通过敲击键盘提供输入数据的线程则属于后者
调度器会依据它对线程的趋向性的猜测把它们分类,并让I/O受限的线程具有更高的动态优先级以优先使用CPU。因为IO操作往往会花费更长时间,应该让它们尽早开始
线程的优先级调整
线程的动态优先级是可以被调度器实时调整的,而与之相对应的线程的静态优先级则只能由应用程序指定
如果应用程序没有显式指定一个线程的静态优先级,那么它将被设定为0
调度器并不会改变线程的静态优先级。线程的动态优先级就是调度器在其静态优先级的基础上调整得出的,它在线程的运行顺序上起到了关键的作用
而线程的静态优先级则决定了线程单次在CPU上运行的最长时间,也就是调度器分配给它的时间片的大小
动态优先级排序
所有等待使用CPU的线程会按照动态优先级从高到低的顺序排列,并依序放到与该CPU对应的运行队列中
因此,下一个运行的线程总是动态优先级最高的那一个
实际上,每一个CPU的运行队列中都包含两个优先级阵列
- 用于存放正在等待运行的线程,称之为
激活的优先级阵列
- 用于存放已经运行过但还未完成的线程,称之为
过期的优先级阵列
优先级阵列是一个由若干个链表组成的数组。一个链表只会包含具有相同优先级的线程,而一个线程也只会放到与其优先级相对应的那个链表中
当一个线程放入某个优先级阵列时,实际上就放到了与其优先级相对应的那个链表的末尾处
优先队列的运行
下一个运行的线程总是会从激活的优先级阵列中选出
如果调度器发现某个线程已经占用了CPU很长时间(该时间只会小于或等于给予线程的时间片)
并且激活的优先级阵列中还有优先级与它相同的线程在等待运行,那么调度器就会让那个等待的线程在CPU上运行,而被换下的线程会排入过期的优先级
当激活的优先级阵列中没有待运行的线程时,调度器会把这两个优先级阵列的身份互换,即之前的激活优先级阵列成为新的过期的优先级阵列,而之前的过期的优先级阵列则会成为新的激活的优先级阵列
如此一来,之前被放入过期的优先级阵列的线程就又有机会运行了
线程的状态
线程会因等待某个事件或条件的发生而加入到对应的等待队列中,并随即进入睡眠状态
当事件发生或条件满足时,内核会通知对应的等待队列中的所有线程,这些线程会因此而被唤醒并从等待队列转移至适当的运行队列中
调度器往往会稍稍调高唤醒的线程的动态优先级
线程与CPU
如果当前计算机上有多个CPU,那么平衡它们之间的负载也将会调度器的职责之一
调度器会尽量使一个线程在一个特定的CPU上运行。这样就可以维持高速缓存的高命中率以及高效使用就近的内存
有时候,一个CPU需要运行太多线程以至于造成了多CPU之间负载的不平衡。也就是说,一些CPU过于忙碌,另一些被闲置
在这种情况下,调度器会把一些原来在较忙碌CPU上运行的线程迁移至其他较空闲的CPU上运行
由于内核会为每个CPU建立一个运行队列,所以线程的这种迁移并不困难