3、先来先服务(FCFS):算法原理、convoy effect(护航效应)分析、适用场景

先来先服务,简称 FCFS。这个名字起得很直白——谁先到,谁先上。我刚开始学操作系统时,觉得这算法也太简单了吧?不就是排个队嘛。但后来在项目中吃过亏,才发现这背后藏着不少门道。

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

FCFS 的核心思想,说白了就是维护一个就绪队列。新来的进程排到队尾,调度器从队头取进程执行。嗯,跟食堂打饭一个道理。

具体流程是这样的:

  • 进程到达时,PCB(进程控制块)被插入就绪队列尾部
  • 调度器每次从队列头部取出一个进程
  • 该进程一直运行,直到主动释放 CPU(比如 I/O 请求)或终止
  • 然后调度器再取下一个队首进程

代码实现也非常直白:

// FCFS 调度器核心逻辑(简化版)
struct pcb *ready_queue_head = NULL;
struct pcb *ready_queue_tail = NULL;

void enqueue(struct pcb *proc) {
    if (ready_queue_tail == NULL) {
        ready_queue_head = ready_queue_tail = proc;
    } else {
        ready_queue_tail->next = proc;
        ready_queue_tail = proc;
    }
    proc->next = NULL;
}

struct pcb *dequeue(void) {
    if (ready_queue_head == NULL) return NULL;
    struct pcb *proc = ready_queue_head;
    ready_queue_head = ready_queue_head->next;
    if (ready_queue_head == NULL) ready_queue_tail = NULL;
    return proc;
}

void fcfs_scheduler(void) {
    while (1) {
        struct pcb *current = dequeue();
        if (current == NULL) {
            // 没有进程可运行,CPU 空闲
            continue;
        }
        // 运行当前进程,直到它主动让出 CPU
        run_process(current);
    }
}

你看,代码就这么几行。但别小看它,很多实时系统、批处理系统至今还在用。

3.2 Convoy Effect(护航效应):一个慢进程拖垮所有人

这是 FCFS 最要命的问题。我当年在一个嵌入式项目中就踩过这个坑。

什么叫护航效应?想象一下:一个耗时很长的 CPU 密集型进程排在前面,后面跟着一堆短进程。长进程霸占 CPU 不放,短进程只能干等。就像一艘大货轮在前面慢悠悠地开,后面一队快艇全被堵住了。

为什么会这样?因为 FCFS 是非抢占式的。一旦进程获得 CPU,除非它自己主动放弃,否则没人能把它赶下来。

典型场景:

假设有三个进程:P1(CPU 密集,运行 100ms)、P2(I/O 密集,运行 1ms)、P3(I/O 密集,运行 1ms)。

如果到达顺序是 P1 → P2 → P3:

  • P1 先跑 100ms,期间 P2、P3 干等
  • P2 跑 1ms,然后可能发起 I/O
  • P3 跑 1ms,然后可能发起 I/O

平均周转时间 = (100 + 101 + 102) / 3 ≈ 101ms

如果换成短作业优先(SJF),平均周转时间 = (1 + 2 + 102) / 3 ≈ 35ms

差了将近 3 倍!

我曾经在一个数据采集系统中用过 FCFS。有个传感器处理进程特别慢,每次要算 200ms 的傅里叶变换。结果其他几个轻量级进程(比如按键响应、LED 刷新)全被堵死了。用户体验极差——按了按钮要等 200ms 才有反应。后来我改成优先级调度,问题才解决。

避坑指南:

我曾经在项目里犯过一个错误——把 FCFS 用在了交互式系统中。结果用户操作延迟高得离谱。记住:FCFS 不适合交互式场景。如果你发现系统里有一个进程特别能吃 CPU,FCFS 会让其他所有进程都跟着遭殃。

3.3 适用场景:不是不能用,要用对地方

FCFS 虽然简单,但也不是一无是处。我个人觉得,它最适合以下场景:

场景 原因 我的经验
批处理系统 所有作业性质相似,没有交互需求 我在一个离线数据分析工具里用过,效果不错
实时系统中的非关键任务 对延迟不敏感的后台任务 日志写入、数据备份这类任务可以用
进程执行时间相近的场景 护航效应不明显 比如所有进程都是 1-2ms 的短任务
系统负载极低时 队列几乎为空,调度开销最小化 单用户嵌入式设备,偶尔跑一个任务

你想想看,FCFS 最大的优势是什么?实现简单、没有饥饿问题(每个进程迟早都能轮到)、上下文切换开销小。这些特点在某些场景下反而是宝贝。

我的建议:

如果你不确定该不该用 FCFS,先问自己三个问题:

  1. 系统里有没有 CPU 密集型的长进程?如果有,慎用。
  2. 用户对响应时间有要求吗?如果有,别用。
  3. 进程到达时间是否相对集中?如果分散,FCFS 的公平性反而好。

3.4 知识体系总览

下面这张图是我自己整理的 FCFS 知识结构,方便你快速回顾:

FCFS 先来先服务调度 算法原理 • 非抢占式调度 • 就绪队列 FIFO • 进程主动让出 CPU • 实现极其简单 护航效应 (Convoy Effect) • 长进程阻塞短进程 • 平均周转时间恶化 • I/O 密集型进程受影响 • 交互体验变差 ⚠ 核心缺陷 适用场景 • 批处理系统 • 非交互式后台任务 • 进程执行时间相近 • 系统负载极低时 ★ 选对场景很重要 优缺点对比 ✅ 优点 实现简单 · 无饥饿 · 开销小 ❌ 缺点 护航效应 · 平均等待时间长 · 不适合交互

嗯,FCFS 就是这么个算法。简单,但坑也不少。我个人觉得,学调度算法不能光看理论,一定要结合自己的项目去体会。你想想看,护航效应这个坑,我当年可是实实在在踩进去过。希望你能从我的经验里少走些弯路。


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