Task1.5 Priority Scheduling
本Task较为复杂,但只要思路清晰,知道每个方法的用途便可以轻松编写,下面我以五个方面来阐述它:
- 实验要求
- 实验关键代码
- 实验测试代码
- 代码分析
- 测试结果分析
实验要求
- 通过完成PriorityScheduler类来实现优先级调度;
- 为了使用优先级调度,你需要在 nachos.conf 文件中更改一行内容,需要将ThredededKernel.scheduler的值由nachos.threads.RoundRobinScheduler更改为nachos.threads.PriorityScheduler;
- 优先级调度应该选择一个有效优先级最高的线程出队列,如果多个线程有效优先级相同,则应该选择等待时间最久的;
- 优先级调度中需要处理优先级倒置问题(可以通过优先级捐赠的方法解决);
实验关键代码
变量声明
- PriorityQueue类
protected KThread lockHolder = null; //队列头
protected LinkedList<KThread> waitList = new LinkedList<KThread>();
- ThreadState类
protected int effectivePriority = -2;//有效优先级初始化为-2
protected final int invalidPriority = -1;//无效优先级
protected HashSet<nachos.threads.PriorityScheduler.PriorityQueue> acquired = new HashSet<nachos.threads.PriorityScheduler.PriorityQueue>();//等待该线程的所有优先队列(每个优先队列里有等待线程),包括等待锁,等待join方法的队列
实现方法
PriorityQueue类
- waitForAccess()方法
public void waitForAccess(KThread thread) {
Lib.assertTrue(Machine.interrupt().disabled());
getThreadState(thread).waitForAccess(this);
}
- acquire(KThread thread)方法
public void acquire(KThread thread) {
Lib.assertTrue(Machine.interrupt().disabled());
getThreadState(thread).acquire(this);
}
- nextThread()方法
public KThread nextThread() {
Lib.assertTrue(Machine.interrupt().disabled());
ThreadState x = pickNextThread();//下一个选择的线程
if(x == null)//如果为null,则返回null
return null;
KThread thread = x.thread;
getThreadState(thread).acquire(this);//将得到的线程改为this线程队列的队列头
return thread;//将该线程返回
}
- pickNextThread()方法
protected ThreadState pickNextThread() {
java.util.Iterator i = waitList.iterator();
KThread nextthread;
if(i.hasNext()){
nextthread = (KThread)i.next();//取出下一个线程
//System.out.println(nextthread.getName());
KThread x = null;
while(i.hasNext()){//比较线程的有效优先级,选出最大的,如果优先级相同,则选择等待时间最长的
x = (KThread)i.next();
//System.out.println(x.getName());
int a = getThreadState(nextthread).getEffectivePriority();
int b = getThreadState(x).getEffectivePriority();
if(a<b){
nextthread = x;
}
}
return getThreadState(nextthread);
}else
return null;
}
ThreadState类
- getEffectivePriority()方法
public int getEffectivePriority() {
Lib.assertTrue(Machine.interrupt().disabled());
if(effectivePriority == invalidPriority&&!acquired.isEmpty()){
effectivePriority = priority;//先将自己的优先级赋给有效优先级
for(Iterator i = acquired.iterator();i.hasNext();){//比较acquired中的所有等待队列中的所有线程的优先级
for(Iterator j = ((PriorityQueue)i.next()).waitList.iterator();j.hasNext();){
ThreadState ts = getThreadState((KThread)j.next());
if(ts.priority>effectivePriority){
effectivePriority = ts.priority;
}
}
}
return effectivePriority;
}else{
if(effectivePriority==-2){ //表明该优先级线程队列不存在优先级捐赠
return priority;
}
return effectivePriority;//如果该线程没有执行,那么它之前算的有效优先级不必重新再算一遍
}
}
- waitForAccess(PriorityQueue waitQueue)方法
public void waitForAccess(PriorityQueue waitQueue) {
waitQueue.waitList.add(this.thread);//将调用线程加入到等待队列
}
- acquire(PriorityQueue waitQueue)方法
public void acquire(PriorityQueue waitQueue) {
waitQueue.waitList.remove(this.thread);//如果这个队列中存在该线程,删除
waitQueue.lockHolder = this.thread;//对于readyQueue来讲,lockHolder为执行线程;对于Lock类的waitQueue来讲,lockHolder为持锁者;对于waitForJoin队列来讲,lockHolder为执行join方法的线程。
if(waitQueue.transferPriority){//如果存在优先级翻转,则执行下面操作
this.effectivePriority = invalidPriority;
acquired.add(waitQueue);//将等待该线程的队列加入该线程的等待队列集合中
}
}
实验测试代码
private static class PingTest implements Runnable {
PingTest(int which) {
this.which = which;
}
public void run() {
for (int i=0; i<5; i++) {
System.out.println("*** thread " + which + " looped "
+ i + " times");
KThread.currentThread().yield();
}
}
private int which;
}
public static void PriorityTest(){
boolean status = Machine.interrupt().disable();//关中断,setPriority()函数中要求关中断
final KThread a = new KThread(new PingTest(1)).setName("thread1");
new PriorityScheduler().setPriority(a,2);
System.out.println("thread1的优先级为:"+new PriorityScheduler().getThreadState(a).priority);
KThread b = new KThread(new PingTest(2)).setName("thread2");
new PriorityScheduler().setPriority(b,4);
System.out.println("thread2的优先级为:"+new PriorityScheduler().getThreadState(b).priority);
KThread c = new KThread(new Runnable(){
public void run(){
for (int i=0; i<5; i++) {
if(i==2)
a.join();
System.out.println("*** thread 3 looped "
+ i + " times");
KThread.currentThread().yield();
}
}
}).setName("thread3");
new PriorityScheduler().setPriority(c,6);
System.out.println("thread3的优先级为:"+new PriorityScheduler().getThreadState(c).priority);
a.fork();
b.fork();
c.fork();
Machine.interrupt().restore(status);
}
代码分析
思路分析
所谓线程调度,以我的个人理解,核心就是怎样选择就绪队列中下一个要执行的线程。本实验通过有效优先级比较,选择有效优先级高的为下一个执行的线程;除此之外,本实验要考虑优先级倒置的问题,优先级倒置问题可以通过捐献优先级来解决。
方法解释
名词解释
队列头(lockHolder):每个队列都有一个队列头,该队列头不在队列里,但只有这个队列头先执行,队列里的线程才有可能执行。
无效优先级(invalidPriority):每次调用该方法acquire(PriorityQueue waitQueue)时,this线程的有效优先级就会设置为无效优先级;
PriorityQueue类
void waitForAccess(KThread thread)方法
将参数中线程加入到this队列中,代码实现中调用了ThreadState类中的waitForAccess(PriorityQueue waitQueue)方法。
void acquire(KThread thread)方法
方法解释
激活this队列(参数中的线程会成为this队列的队列头,但并不在队列里面);
代码实现解释
内部代码调用了ThreadState类中的acquire(KThread thread)方法;
使用范围
这个方法仅仅当this队列中没有元素时才可能调用。比如当一个线程正在申请没有线程正在等待的锁时,会调用该方法。
查看代码会发现,有三个地方使用了该方法:
1、 第一处是readyQueue,当nachos系统第一个线程执行时,会调用readyQueue.acquire(this);
2、第二处是当调用锁的acquire方法时,如果该锁没有持锁者(即没有线程等待该锁时)会调用;
3、第三处为KThread的join()方法,当调用join()方法时,会执行waitForJoinQueue.acquire(this);KThread nextThread()方法
方法解释
决定this队列中下一个最可能执行的线程【如果this是readyQueue,则选择是下一个执行的线程】,最可能执行的线程是队列中有效优先级最高的线程。
代码实现解释
首先调用pickNextThread()方法来得到this队列下一个最可能执行的线程,如果返回为空,则该方法也返回空;如果不为空,则将返回的线程(假设线程A)设置为该队列的队列头【调用ThreadState类中的acquire(PriorityQueue q)方法】,最后返回线程A即可。
ThreadState pickNextThread()方法
方法解释:选择下一个有效优先级最高的线程;
代码实现解释
扫描this队列中的每一个线程,通过比较每个线程的有效优先级,来选出最大有效优先级对应的线程。根据题意如果有效优先级相同,则应该选择等待时间最长的线程。对于这一点,我没有设置每个线程的等待时间,因为我的线程队列的底层实现是LinkedList,所以相同有效优先级,只要优先选择该链表前面的线程即可解决这个问题。(在代码中是这样实现的,只有当后一个线程的有效优先级大于前一个线程的有效优先级,变量nextThread的值才会改变)。
ThreadState类
int getEffectivePriority()方法
方法解释
该方法通过比较this线程的acquired中所有线程的优先级,将最高优先级捐献给this线程。【acquired为一个HashSet容器,该容器中装有所有等待该线程的队列,这些队列包括锁队列(如果该线程持有几把锁,那么等待该锁的线程队列便会在里面),waitForJoin队列(如果A线程join了该线程,则A线程会进入该线程waitForJoin队列)等,队列里放着等待的线程】
代码实现解释
通过this线程有效优先级的值来进行分支处理。
为了防止每次都对等待this线程的容器进行遍历,进行简单优化:
1、 如果线程的有效优先级等于invalidPriority且acquired容器不为空,则开始扫描容器中的每一个线程,将线程中最大的优先级赋给this线程的有效优先级,然后返回该有效优先级;
2、如果this线程的有效优先级等于-2(每个线程的初始有效优先级均为-2),证明所比较的队列不允许优先级捐赠,则返回this线程的优先级即可;
3、如果this线程的有效优先级不是invalidPriority,也不是-2,即证明this线程的有效优先级之前已经计算过,直接返回该线程的有效优先级即可。void waitForAccess(PriorityQueue waitQueue)方法
将this线程加入到waitQueue中。
void acquire(PriorityQueue waitQueue)方法
方法解释:设置waitQueue的队列头为this线程。【在代码实现中队列头为lockHolder】
使用范围: 只有两处使用到该方法
a、PriorityQueue类中acquire(KThread thread)方法调用了该方法;
b、PriorityQueue类中nextThread()方法调用了该方法。代码实现解释
先将this线程从waitQueue中移除(队列头不能在队列里),在代码中使用remove(this)方法,如果this线程本来就不在waitQueue队列中则返回false,否则返回true;
然后将队列的队列头更换为this线程;判断waitQueue队列是否允许优先级捐赠,如果允许,则将this线程的有效优先级设置为无效优先级,并将该队列加入到acquired容器中。否则不作处理【即如果不允许优先级捐赠,则有效优先级仍然为初始值-2】。
关键点和难点
关键点
理解线程调度的概念;
难点
理解PriorityQueue类中acquire方法和ThreadState类中的acquire方法的区别; 怎样来保存等待线程的那些队列,在本实验中采取了HashSet容器; 如何做到避免重复计算有效优先级,通过设置无效优先级来处理; 如何让不允许优先级捐赠的队列避免计算有效优先级,通过设置有效优先级的初始值可以解决; 理解优先级倒置问题的核心,并理解怎样解决。
测试结果分析
测试结果截图
测试结果解释
使用join()方法来对编写的程序进行测试。
首先给三个线程分别设置了优先级为2,4,6;如果不执行join()方法,必定会按照优先级高低顺序执行(线程3先执行,然后执行线程2,最后执行线程1);但在测试程序中当优先级为6的线程(线程3)执行到第3次时,调用了a.join()方法;如果没有发生优先级捐赠的话,线程2应该先执行,因为它的优先级是4,但由于发生了优先级捐赠,线程3将优先级捐赠给了线程1,所以线程1的有效优先级现在是6,故结果中出现了线程1先执行,然后线程3继续执行,最后线程2才执行。