异步I/O和同步I/O

发布于:2025-08-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

点外卖” 和 “自己做饭” 的生活场景来类比 Linux 中的 异步 I/O(AIO) 和 同步 I/O(Synchronous I/O)

1. 同步 I/O(Synchronous I/O)—— 自己做饭

场景

假设你饿了,决定 自己做饭

  1. 第一步:去菜市场买菜(相当于 read() 从磁盘读取数据)
    • 你必须亲自去菜市场,站在摊位前等老板把菜给你(阻塞)。
    • 如果菜市场没菜了,你得一直等(数据未就绪时阻塞)。
  2. 第二步:回家做饭(相当于 CPU 处理数据)
    • 买菜回来后,你才能开始洗菜、切菜、炒菜(顺序执行)。
  3. 第三步:吃饭(相当于程序继续执行后续逻辑)
    • 饭做好后,你才能吃(必须等前一步完成)。

同步 I/O 的特点

  • 阻塞:你必须亲自完成每一步,不能同时做其他事(比如不能一边买菜一边炒菜)。
  • 顺序执行:必须按 买菜 → 做饭 → 吃饭 的顺序来,不能跳过或并行。
  • 简单但低效:如果买菜或做饭很慢,你只能干等着,浪费时间。

Linux 中的同步 I/O 例子

  • read() / write():直接读写文件,如果数据没准备好,程序会卡住(阻塞)。
  • epoll() + 非阻塞 I/O:虽然可以同时监控多个文件描述符,但每次读写仍然需要手动检查数据是否就绪(类似“频繁去菜市场看菜到了没”)。

2. 异步 I/O(Asynchronous I/O, AIO)—— 点外卖

场景

假设你饿了,决定 点外卖

  1. 第一步:在手机上下单(相当于提交异步 I/O 请求)
    • 你只需要在美团/饿了么上点“提交订单”(非阻塞),然后可以继续刷抖音、打游戏(程序继续执行其他任务)。
  2. 第二步:外卖小哥送餐(相当于内核在后台处理 I/O)
    • 商家接单、做饭、打包、骑手取餐、送货,这些过程你完全不用管(内核在后台完成 I/O 操作)。
  3. 第三步:收到外卖通知(相当于回调函数或信号通知)
    • 手机“叮”一声,提示“外卖已送达”(通过回调或信号通知程序 I/O 完成)。
    • 你下楼取餐,然后吃饭(程序处理完成后的逻辑)。

异步 I/O 的特点

  • 非阻塞:下单后你可以继续干其他事,不用站在厨房等饭做好。
  • 并行处理:外卖小哥、商家、骑手可以同时工作(内核在后台高效处理 I/O)。
  • 高效但复杂:你需要留手机号(回调函数)或设置提醒(信号),否则可能错过外卖。

Linux 中的异步 I/O 例子

  • io_uring(Linux 5.1+ 推荐)
    • 像“美团外卖”一样高效,支持文件、网络等异步 I/O。
    • 程序提交订单(io_uring_prep_read),然后继续执行其他任务,内核完成后通知(io_uring_wait_cqe)。
  • libaio(较旧)
    • 类似“老式外卖平台”,功能有限,但也能实现异步文件 I/O。

3. 同步 vs 异步:外卖 vs 做饭

对比项 同步 I/O(自己做饭) 异步 I/O(点外卖)
是否阻塞 阻塞(必须亲自等) 非阻塞(下单后可以继续玩)
能否并行 不能(一步接一步) 能(商家、骑手、你同时干活)
效率 低(浪费时间等) 高(充分利用时间)
复杂度 简单(按步骤来) 复杂(需要处理通知)
适用场景 低并发、简单任务(如单线程脚本) 高并发、高性能需求(如 Web 服务器)

4. 实际代码类比

(1) 同步 I/O(自己做饭)

#include <unistd.h>
#include <stdio.h>

int main() {
    char buf[1024];
    // 阻塞式读取文件(相当于站在菜市场等菜)
    ssize_t n = read(STDIN_FILENO, buf, sizeof(buf)); 
    if (n > 0) {
        // 读取完成后才能继续处理(相当于做饭)
        write(STDOUT_FILENO, buf, n); 
    }
    return 0;
}
  • 问题:如果 read() 很慢(比如从硬盘读取大文件),程序会卡住,无法做其他事。

(2) 异步 I/O(点外卖)

#include <liburing.h>
#include <fcntl.h>
#include <stdio.h>

void callback(struct io_uring_cqe *cqe) {
    printf("外卖已送达!读取了 %d 字节\n", cqe->res);
}

int main() {
    struct io_uring ring;
    io_uring_queue_init(32, &ring, 0); // 初始化“外卖平台”

    int fd = open("test.txt", O_RDONLY);
    char buf[1024];
    
    // 提交异步读请求(相当于下单)
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, fd, buf, sizeof(buf), 0);
    io_uring_submit(&ring); // 告诉内核:“我点单了,你去做吧!”

    printf("下单成功!继续刷抖音...\n"); // 程序继续执行其他任务

    // 稍后检查外卖状态(相当于等手机通知)
    struct io_uring_cqe *cqe;
    io_uring_wait_cqe(&ring, &cqe);
    callback(cqe); // 处理完成事件(取外卖)
    io_uring_cqe_seen(&ring, cqe); // 标记“已取餐”

    io_uring_queue_exit(&ring);
    close(fd);
    return 0;
}
  • 优势:提交请求后,程序可以继续做其他事,内核在后台高效处理 I/O。

5. 总结

  • 同步 I/O(自己做饭):简单但低效,适合简单任务。
  • 异步 I/O(点外卖):高效但复杂,适合高并发场景(如 Nginx、Redis、数据库)。
  • Linux 推荐
    • 高性能文件 I/O:io_uring(现代外卖平台)
    • 网络 I/O:epoll() + 非阻塞 I/O(类似“多线程做饭”)

网站公告

今日签到

点亮在社区的每一天
去签到