先来先服务(FCFS)调度:算法原理、优缺点分析、适用场景、代码实现示例

一、算法原理:排队,就这么简单

先来先服务(First Come First Served,简称 FCFS)调度算法,说白了就是「谁先到谁先上」。

你想想看,去银行办业务、去食堂打饭,是不是都得排队?FCFS 就是这个道理。任务按照到达就绪队列的先后顺序,一个一个地被 CPU 执行。先来的任务先获得 CPU 使用权,直到它主动释放(比如任务完成、或者因为等待 I/O 而阻塞),下一个任务才能开始运行。

我记得刚入行那会儿,带我的老工程师跟我说:「别把调度想得太复杂,FCFS 就是最朴素的排队。」我当时还不太理解,后来自己写了几年代码才明白——简单,往往意味着可靠。

核心规则就两条:

  • 非抢占式:一旦任务获得 CPU,就会一直运行到结束或主动阻塞。中途不会被其他任务打断。
  • 按到达时间排序:调度器维护一个就绪队列,新来的任务排到队尾,调度器每次从队头取任务执行。

关键点:FCFS 的调度决策只依赖于任务的到达时间,不依赖任务的执行时间、优先级或其他任何属性。公平吗?公平。高效吗?不一定。

二、优缺点分析:公平背后的代价

优点

  • 实现极其简单:只需要一个 FIFO 队列,不需要复杂的排序或优先级计算。代码量少,bug 也少。
  • 公平性直观:每个任务都有机会被执行,不会出现「饿死」现象。你想想看,只要排队,总能轮到你。
  • 适合长任务:如果任务执行时间都比较长且稳定,FCFS 的调度开销几乎可以忽略不计。

缺点

  • 平均等待时间长:这是 FCFS 最大的硬伤。短任务如果排在长任务后面,等待时间会非常长。
  • 不利于交互式系统:用户按一下键盘,可能要等好几秒才有响应。我在项目中遇到过这种情况——一个后台数据采集任务占着 CPU 不放,前台界面直接卡死,用户投诉电话都打爆了。
  • 对突发任务不友好:紧急任务即使优先级再高,也得老老实实排队。这在实时系统中是致命的。

避坑指南:我曾经在一个工业控制项目中用了 FCFS,结果一个传感器数据采集任务因为排在长计算任务后面,导致控制指令延迟了 200ms,设备直接报警停机。从那以后,我对 FCFS 的适用场景变得非常谨慎。

三、适用场景:什么时候该用它?

FCFS 不是万能的,但在某些场景下,它反而是最优解。

场景 说明 推荐度
批处理系统 任务执行时间相近,且没有交互需求 ★★★★★
单任务嵌入式设备 系统只运行一个主任务,偶尔处理中断 ★★★★☆
实时性要求低的系统 对响应时间没有严格 deadline 要求 ★★★☆☆
交互式系统 用户需要即时反馈 ★☆☆☆☆
硬实时系统 任务必须在规定时间内完成 ☆☆☆☆☆

我个人习惯在项目初期先用 FCFS 做原型验证。因为实现快、逻辑简单,等系统跑通了再根据实际需求换成更复杂的调度算法。你想想看,如果一开始就上优先级抢占调度,调试起来得多头疼?

四、代码实现示例

下面是一个用 C 语言实现的 FCFS 调度器示例。我尽量写得贴近实际嵌入式开发风格,而不是教科书上的伪代码。

#include <stdio.h>
#include <stdlib.h>

// 任务控制块
typedef struct {
    int task_id;        // 任务ID
    int arrival_time;   // 到达时间
    int burst_time;     // 执行时间
    int start_time;     // 开始时间
    int finish_time;    // 完成时间
    int waiting_time;   // 等待时间
    int turnaround_time;// 周转时间
} Task;

// 按到达时间排序(冒泡排序,简单易懂)
void sort_by_arrival(Task tasks[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (tasks[j].arrival_time > tasks[j + 1].arrival_time) {
                Task temp = tasks[j];
                tasks[j] = tasks[j + 1];
                tasks[j + 1] = temp;
            }
        }
    }
}

// FCFS 调度核心
void fcfs_schedule(Task tasks[], int n) {
    int current_time = 0;
    
    for (int i = 0; i < n; i++) {
        // 如果当前时间小于任务到达时间,CPU 空闲等待
        if (current_time < tasks[i].arrival_time) {
            current_time = tasks[i].arrival_time;
        }
        
        tasks[i].start_time = current_time;
        tasks[i].finish_time = current_time + tasks[i].burst_time;
        tasks[i].turnaround_time = tasks[i].finish_time - tasks[i].arrival_time;
        tasks[i].waiting_time = tasks[i].start_time - tasks[i].arrival_time;
        
        current_time = tasks[i].finish_time;
    }
}

// 打印调度结果
void print_schedule(Task tasks[], int n) {
    printf("任务ID | 到达时间 | 执行时间 | 开始时间 | 完成时间 | 等待时间 | 周转时间\n");
    printf("----------------------------------------------------------------\n");
    
    float avg_waiting = 0, avg_turnaround = 0;
    for (int i = 0; i < n; i++) {
        printf("  %2d   |    %2d    |    %2d    |    %2d    |    %2d    |    %2d    |    %2d\n",
               tasks[i].task_id, tasks[i].arrival_time, tasks[i].burst_time,
               tasks[i].start_time, tasks[i].finish_time,
               tasks[i].waiting_time, tasks[i].turnaround_time);
        avg_waiting += tasks[i].waiting_time;
        avg_turnaround += tasks[i].turnaround_time;
    }
    
    printf("\n平均等待时间: %.2f\n", avg_waiting / n);
    printf("平均周转时间: %.2f\n", avg_turnaround / n);
}

int main() {
    // 模拟一组任务
    Task tasks[] = {
        {1, 0, 5},
        {2, 2, 3},
        {3, 4, 1},
        {4, 6, 4}
    };
    int n = sizeof(tasks) / sizeof(tasks[0]);
    
    // 先按到达时间排序
    sort_by_arrival(tasks, n);
    
    // 执行 FCFS 调度
    fcfs_schedule(tasks, n);
    
    // 输出结果
    print_schedule(tasks, n);
    
    return 0;
}

小提示:在实际嵌入式系统中,任务队列通常用链表实现,而不是数组。因为任务可能动态创建和销毁。我习惯用双向链表,这样插入和删除都方便。

五、核心逻辑流程图

下面我用一张 SVG 图来展示 FCFS 调度的核心流程。嗯,画图比写代码还费劲,但一张好图胜过千言万语。

FCFS 调度核心流程图 开始 初始化就绪队列(FIFO) 队列为空? 结束 取队头任务执行 (非抢占式,直到完成或阻塞) 任务完成,从队列移除

从流程图可以看出,FCFS 的逻辑非常直白:循环判断队列是否为空,不为空就取队头任务执行,执行完再取下一個。没有优先级判断,没有时间片轮转,就这么简单粗暴。

总结一下:FCFS 是最基础的调度算法,理解它对于学习更复杂的调度策略(如短作业优先、时间片轮转、优先级调度)非常有帮助。我个人建议初学者先把 FCFS 吃透,再去看那些花里胡哨的调度算法。地基打不牢,楼盖得再高也得塌。


公众号:蓝海资料掘金营,微信deep3321