最近在准备华为OD机考时遇到一道关于单核CPU任务调度的题目,要求用Java实现一个模拟调度系统。这类题目在操作系统原理和实际开发中都非常典型,尤其适合考察候选人对任务调度算法的理解与编码能力。题目设定在双机位监控环境下,意味着需要考虑更严格的防作弊机制,这对算法的时间复杂度和空间复杂度都提出了更高要求。
单核CPU调度看似简单,但实际涉及多个关键维度:任务队列管理、调度策略选择、上下文切换模拟以及性能指标统计。在机考场景下,还需要特别注意代码的鲁棒性和边界条件处理,因为自动判题系统往往会对极端输入进行测试。
题目给出一个任务列表,每个任务包含到达时间arrivalTime和执行时间executionTime。单核CPU需要按照以下规则处理任务:
示例输入:
code复制[[1,3], [2,5], [4,2]]
预期输出:
code复制[0, 1, 3]
在华为OD的双机位监控环境下,解题时需要特别注意:
采用两个优先队列来管理任务:
java复制// 按到达时间排序的队列
PriorityQueue<Task> arrivalQueue = new PriorityQueue<>(
(a,b) -> a.arrival - b.arrival
);
// 按执行时间排序的待调度队列
PriorityQueue<Task> readyQueue = new PriorityQueue<>(
(a,b) -> a.execution != b.execution ?
a.execution - b.execution :
a.arrival - b.arrival
);
这种双队列结构可以高效处理任务到达和调度的时序问题。arrivalQueue保证我们按时间顺序处理任务,readyQueue则实现了SJF调度策略。
java复制public int[] scheduleTasks(int[][] tasks) {
// 初始化队列
for(int[] t : tasks) {
arrivalQueue.offer(new Task(t[0], t[1]));
}
int currentTime = 0;
int[] waitTimes = new int[tasks.length];
int index = 0;
while(!arrivalQueue.isEmpty() || !readyQueue.isEmpty()) {
// 将已到达任务移入ready队列
while(!arrivalQueue.isEmpty() &&
arrivalQueue.peek().arrival <= currentTime) {
readyQueue.offer(arrivalQueue.poll());
}
if(readyQueue.isEmpty()) {
// CPU空闲,快进到下一个任务到达时间
currentTime = arrivalQueue.peek().arrival;
continue;
}
// 执行当前任务
Task current = readyQueue.poll();
waitTimes[index++] = currentTime - current.arrival;
currentTime += current.execution;
}
return waitTimes;
}
该算法的时间复杂度主要来自:
java复制class Task {
int arrival;
int execution;
// 可添加原始索引用于结果映射
int originIndex;
public Task(int a, int e) {
this.arrival = a;
this.execution = e;
}
}
注意:在真实机考环境中,建议将Task类设为static,避免因内部类导致的额外开销。
需要特别注意以下边界情况:
优化后的ready队列比较器:
java复制(a,b) -> {
if(a.execution != b.execution) {
return a.execution - b.execution;
}
// 执行时间相同时,按到达时间排序
return a.arrival - b.arrival;
}
java复制@Test
public void testBasicCase() {
int[][] tasks = {{1,3}, {2,5}, {4,2}};
int[] expected = {0, 1, 3};
assertArrayEquals(expected, scheduleTasks(tasks));
}
java复制@Test
public void testEmptyInput() {
int[][] tasks = {};
int[] expected = {};
assertArrayEquals(expected, scheduleTasks(tasks));
}
java复制@Test
public void testSameArrivalTime() {
int[][] tasks = {{1,5}, {1,3}, {2,1}};
int[] expected = {0, 0, 4};
assertArrayEquals(expected, scheduleTasks(tasks));
}
生成10000个随机任务测试算法稳定性:
java复制@Test
public void testLargeInput() {
int[][] tasks = new int[10000][2];
Random rand = new Random();
for(int i=0; i<tasks.length; i++) {
tasks[i][0] = rand.nextInt(100000);
tasks[i][1] = rand.nextInt(100) + 1;
}
// 仅测试是否正常执行
int[] result = scheduleTasks(tasks);
assertEquals(tasks.length, result.length);
}
java复制System.out.println("Time: " + currentTime);
System.out.println("Ready Queue: " + readyQueue);
code复制时间轴:0-----1-----2-----3-----4-----5-----6
任务1: |=====|
任务2: |=======|
任务3: |==|
java复制assert currentTime >= lastTime : "时间不应回退";
lastTime = currentTime;
若要实现最短剩余时间优先(SRTF)策略,需要:
扩展到多核场景时需要考虑:
增加优先级字段后,比较器需要修改为:
java复制(a,b) -> {
if(a.priority != b.priority) {
return a.priority - b.priority;
}
if(a.execution != b.execution) {
return a.execution - b.execution;
}
return a.arrival - b.arrival;
}
在实际系统开发中,还需要考虑:
比如Linux的CFS调度器就使用了红黑树来管理任务队列,其时间复杂度为O(logn)。而Windows则采用多优先级队列的方式,不同优先级的任务有不同的调度策略。
我在实际准备过程中发现,这类调度问题通常有固定模式:
最后一个小技巧:在华为OD机考中,即使无法完全解决问题,也要确保代码能够处理各种异常输入而不崩溃,这通常能获得部分分数。对于实在无法解决的case,可以添加适当的注释说明你的思路,有时也会得到酌情给分。