调度算法

调度算法

1、先来先服务

先来先服务(FCFS),系统将按照作业到达的先后顺序 来进行调度,它会优先调度在系统中等待最久的作业

现在很少使用FCFS做主调度算法了,而是选择它与其他算法结合使用。

代码实现

c++ 复制代码
struct Process {
    int pid;
    int arrival_time;
    int burst_time;
    int completion_time;
    int turnaround_time;
    int waiting_time;
};

// 时间排序
bool compare_arrival_time(const Process& a, const Process& b) {
    return a.arrival_time < b.arrival_time;
}

// FCFS 调度函数
void fcfs_scheduling(std::vector<Process>& processes) {
    std::sort(processes.begin(), processes.end(), compare_arrival_time);
    int current_time = 0;
    for (auto& process : processes) {
        if (current_time < process.arrival_time) {
            current_time = process.arrival_time;
        }
        process.completion_time = current_time + process.burst_time;
        process.turnaround_time = process.completion_time - process.arrival_time;
        process.waiting_time = process.turnaround_time - process.burst_time;
        current_time = process.completion_time;
    }
}
c++ 复制代码
int main() {
    std::vector<Process> processes = {
        {1, 0, 5, 0, 0, 0},
        {2, 1, 3, 0, 0, 0},
        {3, 2, 8, 0, 0, 0},
        {4, 3, 6, 0, 0, 0}
    };

    fcfs_scheduling(processes);

    std::cout << "进程ID\t到达时间\t执行时间\t完成时间\t周转时间\t等待时间" << std::endl;
    for (const auto& process : processes) {
        std::cout << process.pid << "\t" << process.arrival_time << "\t\t" << process.burst_time << "\t\t"
                  << process.completion_time << "\t\t" << process.turnaround_time << "\t\t" << process.waiting_time << std::endl;
    }

    return 0;
}    

2、短作业优先

实际上,短作业占很大比例,因此产生了短作业优先 调度算法(SJF)。SJF以作业长短 计算优先级,作业越短,优先级越高。当把SJF调度算法用于作业调度时,它将从外存的作业后备队列中选择估计运行时间最短 的作业,并优先将它调入内存运行 。当SJF调度算法用于进程调度时,它将从就绪队列中选择估计运行时间最短的进程,并为之分配CPU运行 。但SJF也存在着一些明显的缺点:必须预先知道作业运行时间对长作业不利 ,并且忽视作业的等待时间,使作业等待过长,出现饥饿 、SJF调度算法没有考虑作业的紧迫程度

"饥饿" 是一种现象,指的是一个进程或线程因为长期无法获得所需的资源而无法继续执行。

代码示例

与FCFS主要是调度函数上的不同,结构一致

C++ 复制代码
void sjf_scheduling(std::vector<Process>& processes) {
    std::sort(processes.begin(), processes.end(), compare_arrival_time);
    int current_time = 0;
    int completed = 0;
    std::vector<bool> is_completed(processes.size(), false);

    while (completed != processes.size()) {
        int shortest_index = -1;
        int shortest_burst = 9999;

        for (size_t i = 0; i < processes.size(); ++i) {
            if (!is_completed[i] && processes[i].arrival_time <= current_time && processes[i].burst_time < shortest_burst) {
                shortest_index = i;
                shortest_burst = processes[i].burst_time;
            }
        }

        if (shortest_index == -1) {
            current_time = processes[0].arrival_time;
            for (size_t i = 0; i < processes.size(); ++i) {
                if (!is_completed[i] && processes[i].arrival_time < current_time) {
                    shortest_index = i;
                    shortest_burst = processes[i].burst_time;
                }
            }
        }

        current_time += processes[shortest_index].burst_time;
        processes[shortest_index].completion_time = current_time;
        processes[shortest_index].turnaround_time = processes[shortest_index].completion_time - processes[shortest_index].arrival_time;
        processes[shortest_index].waiting_time = processes[shortest_index].turnaround_time - processes[shortest_index].burst_time;
        is_completed[shortest_index] = true;
        completed++;
    }
}

3、优先级调度算法

优先级调度算法基于进程的紧迫程度 ,由外部赋予优先级。系统将从后备队列中选择优先级最高的作业装入内存 。当把该算法用于进程调度时,系统将从就绪队列中选择具有最高优先级的进程在CPU上运行

3.1 优先级调度算法的类型

非抢占式优先级调度算法一旦分配 ,进程便会一直执行 下去直至完成 ,或者进程因某事件发生而放弃处理机 ,系统便可重新分配处理机

抢占式优先级调度算法 :该算法规定,在把处理机分配 给优先级最高的进程并使之执行时,只要出现了另一个优先级更高 的进程,调度程序就会将处理机分配给新到的优先级更高的进程 。因此,在采用这种调度算法时,每当系统中出现一个新的就绪进程 i时,系统就会将其优先级Pi同正在执行的进程j的优先级Pj进行比较,如果Pi≤Pj,则原进程j继续执行;但如果Pi>Pj,则立即停止原进程j的执行并进行进程切换 ,使新进程i投入执行。抢占式优先级调度算法常用于对实时性要求较高的系统中

3.2 优先级类型

优先级调度算法的关键在于如何确定进程的优先级,以及如何确定应当使用静态优先级,还是动态优先级。

静态优先级 。静态优先级是在创建进程时确定 的,其在进程的整个运行期间保持不变 。优先级是利用某一范围内的一个整数(如0~255的某一整数)来表示的,我们把该整数称为优先数 。确定进程优先级大小的依据有3个:①进程类型 ,通常系统进程(如接收进程、对换进程等)的优先级要高于一般用户进程的优先级;②进程对资源的需求 ,对资源要求少的进程应被赋予较高的优先级;③用户要求 ,根据进程的紧迫程度以及用户所付费用的多少,确定优先级。静态优先级这一方法简单易行,系统开销小,但不够精确,可能会出现优先级低的进程长期未被调度的情况。

动态优先级 。动态优先级是指在创建进程之初先赋予进程一个优先级 ,然后优先级会随进程的推进或等待时间的增加而改变 ,以便获得更好的调度性能。例如,可以规定在就绪队列中的进程,其优先级能随等待时间的增长而提高。若所有的进程都具有相同的优先级初值,则最先进入就绪队列的进程会因优先级变得更高而优先获得处理机,这相当于FCFS调度算法。若所有的就绪进程均具有各不相同的优先级初值,那么对于优先级初值较低 的进程,在等待了足够长的时间 后也可获得处理机 。当采用抢占式优先级调度算法时,若再规定当前进程的优先级随运行时间的推移而下降 ,则可防止一个长作业长期垄断处理机

3.3 高响应比优先队列

高响应优先比(HRRN)调度算法是优先级调度算法的一个特例,通常用于作业调度 。HRRN调度算法则是既考虑了作业的等待时间,又考虑了作业的运行时间,因此其既照顾了短作业,又不会致使长作业的等待时间过长,从而改善了处理机调度的性能.HRRN调度算法为每个作业引入一个动态优先级
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 优先级 = 等待时间 + 要求服务时间 要求服务时间 优先级 = \frac{等待时间 + 要求服务时间}{要求服务时间} </math>优先级=要求服务时间等待时间+要求服务时间
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 优先级 = 响应时间 要求服务时间 优先级 = \frac{响应时间}{要求服务时间} </math>优先级=要求服务时间响应时间

但在使用该算法时,每次调度前需要计算响应比,会增加系统的开销

代码示例

C++ 复制代码
struct Process {
    int pid;
    int arrival_time;
    int burst_time;
    int completion_time;
    int turnaround_time;
    int waiting_time;
    bool completed = false;
};

void hrrn_scheduling(std::vector<Process>& processes) {
    int current_time = 0;
    int completed_processes = 0;
    int total_processes = processes.size();

    while(completed_processes < total_processes) {
        std::vector<Process*> eligible_processes;
        for(auto& process : processes) {
            if(!process.completed && process.arrival_time <= current_time) {
                eligible_processes.push_back(&process);
            }
        }

        if(eligible_processes.empty()) {
            int min_arrival_time = 9999;
            for(const auto& process : processes) {
                if(!process.completed && process.arrival_time < min_arrival_time) {
                    min_arrival_time = process.arrival_time;
                }
            }
            current_time = min_arrival_time;
            continue;
        }

        double max_response_ratio = -1;
        Process* selected_process = nullptr;
        for(auto process : eligible_processes) {
            int waiting_time = current_time - process->arrival_time;
            double response_ratio = (double)(waiting_time + process->burst_time) / process->burst_time;
            if(response_ratio > max_response_ratio) {
                max_response_ratio = response_ratio;
                selected_process = process;
            }
        }

        selected_process->completed = true;
        current_time += selected_process->burst_time;
        selected_process->completion_time = current_time;
        selected_process->turnaround_time = selected_process->completion_time - selected_process->arrival_time;
        selected_process->waiting_time = selected_process->turnaround_time - selected_process->burst_time;
        completed_processes++;
    }
}

4、 轮转调度

在分时系统中,最简单也是较常用的进程调度算法是基于时间片的轮转 (round robin,RR)调度算法。该算法采取了非常公平的处理机分配方式,即让就绪队列上的每个进程每次仅运行一个时间片 。如果就绪队列上有n个进程,则每个进程每次大约可获得1/n的处理机时间。当时间片未用完但进程完成 时和时间片结束 时均会引起进程的切换

时间片大小的确定应略大于一次典型交互时间。

代码示例

C++ 复制代码
struct Process {
    int pid;
    int arrival_time;
    int remaining_time;
    int completion_time;
    int turnaround_time;
    int waiting_time;
};
C++ 复制代码
void rr_scheduling(std::vector<Process>& processes) {
    int time_quantum = 4;
    int current_time = 0;
    int total_processes = processes.size();
    std::vector<Process> completed_processes;
    std::queue<Process*> queue;

    while(completed_processes.size() < total_processes) {
        // 将已到达的进程加入队列
        for(auto& process : processes) {
            if(process.arrival_time <= current_time) {
                bool in_queue = false;
                for(int i = 0; i < queue.size(); ++i) {
                    if(queue.front() == &process) {
                        in_queue = true;
                        break;
                    }
                    queue.push(queue.front());
                    queue.pop();
                }

                bool is_completed = false;
                for(const auto& completed : completed_processes) {
                    if(completed.pid == process.pid) {
                        is_completed = true;
                        break;
                    }
                }
                if(!in_queue && !is_completed) {
                    queue.push(&process);
                }
            }
        }

        if(queue.empty()) {
            // 如果队列空,时间推进到下一个进程到达的时间
            int next_arrival = 9999;
            for(const auto& process : processes) {
                if(process.arrival_time > current_time) {
                    next_arrival = std::min(next_arrival, process.arrival_time);
                }
            }
            current_time = next_arrival;
            continue;
        }

        Process* current_process = queue.front();
        queue.pop();

        if(current_process->remaining_time <= time_quantum) {
            // 若剩余时间小于等于时间片,该进程可完成
            current_time += current_process->remaining_time;
            current_process->remaining_time = 0;
            current_process->completion_time = current_time;
            current_process->turnaround_time = current_process->completion_time - current_process->arrival_time;
            current_process->waiting_time = current_process->turnaround_time - (current_process->completion_time - current_time);
            completed_processes.push_back(*current_process);
        } else {
            // 若剩余时间大于时间片,执行一个时间片后放回队列尾部
            current_time += time_quantum;
            current_process->remaining_time -= time_quantum;
            queue.push(current_process);
        }
    }

    processes = completed_processes;
}

5、多级队列调度

前述的调度算法,当它们被应用于进程调度时,由于系统中仅设置了一个进程就绪队列,换言之,低级调度算法是固定的、单一的 ,且在多处理机系统中,这种低级调度算法实现机制的缺点更为突出,而多级队列(multileved queue)调度算法恰好弥补这一缺点。

多级队列调度算法将系统中的进程就绪队列从一个拆分为若干个,将不同类型或性质的进程固定分配在不同的就绪队列,不同的就绪队列采用不同的调度算法。对每个处理机的调度可以实施各自不同的调度策略。

6、多级反馈调度

前述的调度算法,如未指明进程长度,则SJF和基于长度的抢占式优先调度算法无法使用。而多级反馈调度就可以解决这个问题。

6.1 调度机制

设置多个就绪队列 ,并为每队赋予不同优先级。优先级越高的队列时间片越短

每个队列采用FCFS调度算法 :当新进程进入内存后,首先将它放入第一个队列的末尾 ,按FCFS策略等待调度。当轮到该进程执行时,如果它能在该时间片尚未完成,调度程序将其转入第二个队列的末尾等待调度;如仍未完成,则再将它放入第三个队列,依此类推。当进程最后被降到第n队列后,在第n队列中便采取RR方式运行。

按队列优先级调度 。调度程序首先调度最高优先级队列中的各进程运行,仅当第一队列空闲时,才调度第二队列中的进程运行。如果处理机在第i队列中为某进程服务时,又有新进程进入任一优先级较高的队列 ,则须立即把正在运行的进程放回到第i队列的末尾,并把处理机分配给新到的高优先级进程

6.2 调度性能

多级反馈队列调度算法 中,如果规定第一个队列的时间片略大于多数人机交互所需的处理时间,则能较好地满足各类用户的需要 。①终端型 用户。由于终端型用户提交的作业多属于交互型作业,通常较小,系统只要能使这些作业在第一队列 规定的时间片内完成。②短批处理作业 用户。对于这类作业,如果可在第一队列中执行完成,则能获得与终端型作业一样的响应时间。对于稍长的短作业,也只须在第二和第三队列各执行一个时间片即可完成,其周转时间仍然较短。③长批处理作业用户。对于这类作业,其将依次在第1, 2,...,n个队列中运行,然后再按RR方式运行,用户不必担心其作业长期得不到处理。

7、基于公平原则的调度算法

保证调度算法

保证调度算法向用户所做的并不是优先运行保证,而是明确的性能保证 ,该算法可以做到调度的公平性 。一种比较容易实现的性能保证措施是公平分配处理机 。为了公平起见,须保证每个进程都能获得相同的处理机时间。在实施公平调度算法时,系统必须具有下列功能:

跟踪计算 每个进程自创建以来已经执行的处理时间;②计算每个进程应获得的处理机时间 ;③计算进程获得处理机时间的比率 ,即进程实际执行的处理时间和应获得的处理机时间之比;④比较各进程获得处理机时间的比率 ;⑤调度程序应选择比率最小的进程 ,将处理机分配给它,并让它一直运行,直到它的比率超过最接近它的进程的比率为止。

公平分享调度算法

分配给每个进程相同的处理机时间 ,显然,这对各进程而言体现了一定程度的公平,但如果各用户所拥有的进程数不同 ,就会发生对用户的不公平 问题。在公平分享调度算法中,调度的公平性主要是针对用户 的,即所有用户能获得相同的处理机时间或所要求的时间比例 。然而调度又以进程为基本单位。为此,必须考虑每个用户所拥有的进程数目

相关推荐
龙湾开发1 小时前
计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 05.纹理贴图
c++·笔记·学习·3d·图形渲染·贴图
yasuniko2 小时前
C++线程库
开发语言·c++
还有几根头发呀2 小时前
深入理解C/C++内存管理:从基础到高级优化实践
c++
zxctsclrjjjcph3 小时前
【递归、搜索和回溯】递归、搜索和回溯介绍及递归类算法例题
开发语言·c++·算法·力扣
hallo-ooo4 小时前
【C/C++】范围for循环
c语言·c++
泡泡_02244 小时前
密码学--RSA
c++·密码学
1白天的黑夜14 小时前
动态规划-62.不同路径-力扣(LeetCode)
c++·算法·leetcode·动态规划
似水এ᭄往昔5 小时前
【数据结构】——双向链表
c语言·数据结构·c++·链表
fpcc5 小时前
跟我学C++中级篇——STL容器的查找对比
数据结构·c++
赵和范6 小时前
C++:求分数序列和
开发语言·c++·算法