设计模式与操作系统:当经典遇上底层

说实话,做了这么多年软件,我越来越觉得设计模式这东西,不是凭空想出来的。它其实是对现实世界问题的一种抽象。你想想看,操作系统里那些调度算法、进程状态切换、资源监控,简直就是设计模式的天然试验场。

今天我们就来聊聊三个经典组合:策略模式选调度算法、状态模式管进程状态、观察者模式监控资源。这三个场景,我在做嵌入式系统时都亲手实现过,踩过坑,也填过坑。

核心观点:设计模式不是花架子,操作系统里的调度、状态管理、监控,用对了模式,代码能少写一半,可维护性翻倍。

策略模式:让调度算法可插拔

先说说调度算法。我记得刚入行时,接手一个实时系统,调度算法写死在if-else里。后来需求一变,要加个优先级反转处理,改得我头皮发麻。

策略模式说白了就是:把算法封装成独立的策略类,运行时自由切换。操作系统里的调度算法,比如先来先服务、短作业优先、时间片轮转,每个都是独立的策略。

来看个简化版的Java实现:

// 策略接口
public interface SchedulingStrategy {
    void schedule(List<Process> processes);
}

// 具体策略:先来先服务
public class FCFSStrategy implements SchedulingStrategy {
    @Override
    public void schedule(List<Process> processes) {
        processes.sort(Comparator.comparing(Process::getArrivalTime));
        System.out.println("使用FCFS调度");
    }
}

// 具体策略:时间片轮转
public class RoundRobinStrategy implements SchedulingStrategy {
    private int timeQuantum;
    
    public RoundRobinStrategy(int timeQuantum) {
        this.timeQuantum = timeQuantum;
    }
    
    @Override
    public void schedule(List<Process> processes) {
        System.out.println("使用时间片轮转,时间片=" + timeQuantum);
        // 轮转调度逻辑
    }
}

// 上下文:调度器
public class Scheduler {
    private SchedulingStrategy strategy;
    
    public void setStrategy(SchedulingStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void run(List<Process> processes) {
        strategy.schedule(processes);
    }
}

C++版本也类似,用虚函数实现多态:

class SchedulingStrategy {
public:
    virtual void schedule(std::vector<Process>& processes) = 0;
    virtual ~SchedulingStrategy() = default;
};

class FCFSStrategy : public SchedulingStrategy {
public:
    void schedule(std::vector<Process>& processes) override {
        std::sort(processes.begin(), processes.end(), 
            [](const Process& a, const Process& b) {
                return a.arrivalTime < b.arrivalTime;
            });
        std::cout << "使用FCFS调度" << std::endl;
    }
};

我的经验:我曾经在一个多核调度器里用策略模式,把调度算法做成可配置的。上线后发现,不同负载下切换策略,性能提升30%以上。但要注意——策略切换不能太频繁,否则上下文切换开销会吃掉收益。

状态模式:进程状态的优雅管理

进程状态管理,是操作系统的核心之一。就绪、运行、阻塞、挂起……状态之间转换复杂,用if-else写,代码会变成一团乱麻。

状态模式的核心思想是:把每个状态封装成一个类,状态转换由状态对象自己决定。这样新增状态时,不用改现有代码。

我画了一张状态转换图,帮你理解:

进程状态转换图(状态模式) 就绪 (Ready) 运行 (Running) 阻塞 (Blocked) 挂起就绪 挂起阻塞 调度 时间片用完 I/O请求 I/O完成 挂起 激活 挂起 激活 I/O完成

状态模式的Java实现核心:

// 状态接口
public interface ProcessState {
    void dispatch(ProcessContext context);   // 调度
    void block(ProcessContext context);      // 阻塞
    void unblock(ProcessContext context);    // 唤醒
    void suspend(ProcessContext context);    // 挂起
    void activate(ProcessContext context);   // 激活
}

// 就绪状态
public class ReadyState implements ProcessState {
    @Override
    public void dispatch(ProcessContext context) {
        System.out.println("就绪 -> 运行");
        context.setState(new RunningState());
    }
    
    @Override
    public void suspend(ProcessContext context) {
        System.out.println("就绪 -> 挂起就绪");
        context.setState(new SuspendedReadyState());
    }
    // 其他方法抛出异常或忽略
}

// 运行状态
public class RunningState implements ProcessState {
    @Override
    public void block(ProcessContext context) {
        System.out.println("运行 -> 阻塞");
        context.setState(new BlockedState());
    }
    
    @Override
    public void dispatch(ProcessContext context) {
        System.out.println("时间片用完,运行 -> 就绪");
        context.setState(new ReadyState());
    }
    // ...
}

避坑指南:我曾经在一个项目里,状态转换逻辑散落在各个if-else里,结果加一个「挂起」状态,改了十几个文件。用状态模式后,新增状态只需加一个类,改三个转换方法。但要注意——状态类不能持有上下文的长引用,否则容易内存泄漏。

观察者模式:资源监控的利器

操作系统要监控CPU使用率、内存占用、磁盘I/O……这些监控点变化时,需要通知多个模块(日志、告警、负载均衡)。观察者模式就是干这个的。

观察者模式的核心:被观察者(Subject)维护观察者列表,状态变化时自动通知。这样监控点和处理逻辑解耦。

来看一个CPU监控的简化实现:

import java.util.ArrayList;
import java.util.List;

// 观察者接口
interface Observer {
    void update(double cpuUsage, long memoryUsed);
}

// 被观察者:资源监控器
class ResourceMonitor {
    private List<Observer> observers = new ArrayList<>();
    private double cpuUsage;
    private long memoryUsed;
    
    public void attach(Observer observer) {
        observers.add(observer);
    }
    
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    
    public void setMetrics(double cpuUsage, long memoryUsed) {
        this.cpuUsage = cpuUsage;
        this.memoryUsed = memoryUsed;
        notifyObservers();
    }
    
    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(cpuUsage, memoryUsed);
        }
    }
}

// 具体观察者:日志记录器
class LoggerObserver implements Observer {
    @Override
    public void update(double cpuUsage, long memoryUsed) {
        System.out.println("[日志] CPU: " + cpuUsage + "%, 内存: " + memoryUsed + "MB");
    }
}

// 具体观察者:告警器
class AlertObserver implements Observer {
    private double cpuThreshold;
    
    public AlertObserver(double cpuThreshold) {
        this.cpuThreshold = cpuThreshold;
    }
    
    @Override
    public void update(double cpuUsage, long memoryUsed) {
        if (cpuUsage > cpuThreshold) {
            System.out.println("[告警] CPU使用率超过阈值: " + cpuUsage + "%");
        }
    }
}

C++版本用std::function实现更灵活:

#include <vector>
#include <functional>
#include <algorithm>

class ResourceMonitor {
private:
    std::vector<std::function<void(double, long)>> observers;
    double cpuUsage;
    long memoryUsed;
    
public:
    void attach(std::function<void(double, long)> observer) {
        observers.push_back(observer);
    }
    
    void setMetrics(double cpu, long mem) {
        cpuUsage = cpu;
        memoryUsed = mem;
        for (auto& observer : observers) {
            observer(cpuUsage, memoryUsed);
        }
    }
};

我的习惯:观察者模式里,通知顺序有时候很重要。比如告警要先于日志,否则日志还没写,系统就挂了。我一般用优先级队列来管理观察者,或者用有序列表。

三种模式的协同

实际系统中,这三种模式经常一起用。比如:

  • 策略模式选择调度算法
  • 状态模式管理每个进程的状态
  • 观察者模式监控系统负载,动态切换调度策略

我做过一个自适应调度器,观察者监控到CPU空闲时间低于10%时,自动把调度策略从「时间片轮转」切换到「优先级调度」。这就是三个模式的完美配合。

模式 操作系统场景 核心好处 我的踩坑点
策略模式 调度算法选择 算法可插拔,新增算法不改现有代码 策略切换太频繁,性能反而下降
状态模式 进程状态管理 状态转换清晰,新增状态成本低 状态类容易持有上下文引用导致泄漏
观察者模式 资源监控与告警 监控与处理解耦,扩展方便 通知顺序没控制好,告警先于日志

总结一下:设计模式不是象牙塔里的理论。操作系统这种底层系统,用好了设计模式,代码结构清晰,扩展性极强。下次你写调度器、状态机、监控模块时,不妨试试这三个模式——我保证,你会回来感谢我的。

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