当前位置: 首页 > article >正文

Linux - 五种常见I/O模型

I/O操作 (输入/输出操作, Input/Output) 是指计算机与外部设备就行数据交互的过程.

什么是外部设备: 如键盘, 鼠标, 硬盘, 网卡等.

五种常见的 I/O 模型:

  1. 阻塞 I/O
  2. 非阻塞 I/O
  3. 信号驱动 I/O
  4. I/O 多路复用
  5. 异步 I/O

阻塞 I/O

阻塞 I/O 的特点: 当用户发起 I/O 请求后, 进程/线程就会被阻塞, 直到这个 I/O 操作完成.

1. 发起 I/O 请求 ( read/write ).

2. 如果数据为准备好, 那么进程/线程就会被阻塞, 进入等待状态

3. 数据准备好了, 操作系统将数据复制到用户空间, 进程/线程获取到了数据, 就被唤醒继续执行

就像是一个人去钓鱼, 当他甩鱼竿之后, 就一直盯着鱼竿什么都不做, 直到看见有鱼上钩了, 将鱼竿收起来.

阻塞 I/O: 实现简单, 容易理解.

缺点: 但是效率低下, 因为进程/线程会一直等待 I/O 操作完成, 等待期间不会执行其他的任务, 资源的利用率低.

非阻塞 I/O

上面了解了阻塞 I/O 是一直等待 I/O 操作完成形成阻塞.
那么非阻塞 I/O 也就是当进程/线程发起 I/O 请求后, 即使数据没有准备好, 也会立即返回. 不会被阻塞住.

1. 进程/线程发起非阻塞 I/O

2. 如果数据未准备好, 操作系统就会返回一个错误

3. 进程或线程需要不断的轮询, 检查数据是否准备好

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

#define BUFFER_SIZE 1024

int main() {
    char buffer[BUFFER_SIZE];
    int flags;

    // 获取标准输入的文件描述符的当前标志
    flags = fcntl(STDIN_FILENO, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        return 1;
    }

    // 设置标准输入为非阻塞模式
    if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL");
        return 1;
    }

    while (1) {
        // 尝试从标准输入读取数据
        ssize_t n = read(STDIN_FILENO, buffer, BUFFER_SIZE - 1);
        if (n == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 没有数据可读,进行其他操作
                printf("No input available, doing other work...\n");
                sleep(1);
            } else {
                perror("read");
                break;
            }
        } else if (n > 0) {
            // 读取到数据,处理数据
            buffer[n] = '\0';
            printf("Read input: %s", buffer);
        } else {
            break;
        }        
    }

    return 0;
}

可以看到代码中对于 read 函数的操作需要使用循环不断的检查数据是否准备完成. 这个不断循环的过程就称为轮询.

就像去钓鱼, 我不会一直在那盯着鱼竿, 而是每过一会就去看看有没有鱼上钩, 在这期间可以去看看手机, 刷刷视频.

优点: 如果检测到数据还没准备好, 那么此时程序也可以执行一些耗时不长的任务, 这也是非阻塞 I/O 的一个优点.

缺点: 轮询机制会占用大量的 CPU 资源, 效率低下.

信号驱动 I/O

基于信号通知进程/线程 I/O 操作完成.

1. 程序注册一个信号处理器

2. 当数据准备好了, 操作系统就会发送对应的信号通知程序

3. 程序接收到信号后, 信号处理器就会执行对应操作

钓鱼的时候, 每过一段时间就去看看太麻烦了, 所以在鱼竿上装了一个铃铛, 当有鱼咬钩时, 铃铛就会响. 铃铛响将相当于是信号. 铃铛不响我就继续做我自己的事, 看看手机....

优点: 这也是非阻塞的一个 I/O 模型, 有着非阻塞 I/O 的优点. 通过信号可以准确快速的响应 I/O 事件.

缺点: 那么引入了信号, 程序就必然会变得更加复杂, 容易出现错误.

I/O 多路复用

上面的 I/O 模型中, 都是对某一个 I/O 操作进行管理. 

I/O 多路复用则是对于多个 I/O 操作进行管理, 当某个 文件描述符(fd) 准备好了后, 系统就会发送通知, 此时程序就可以对这些 准备好了的 fd 进行操作

1. 通过 select, poll 或 epoll 监控多个文件描述符 (fd) 的状态

2. 当有 fd 准备好了后, 操作系统通知程序, 程序就对这些准备好的 fd 进行操作

上面钓鱼中, 我只带了一根钓鱼竿 , 那么现在我带了50根钓鱼竿, 50根杆同时钓鱼, 效率大幅提升.

异步 I/O

前面的四种都是同步 I/O. I/O = 等待 + 拷贝. 同步 I/O 至少参与了一个过程 (等待或拷贝).

而异步 I/O 则是两个都不参加. 有操作系统完成 I/O 操作, 操作系统完成后通知进程/线程. 进程/线程可以通过回调函数或事件通知机制获取本次 I/O 操作的结果.

1. 用户进程/线程发起异步 I/O 请求, 直接返回

2. 操作系统负责完成 I/O 操作, 并在完成后通知用户进程/线程

3. 用户线程通过回调函数或事件通知机制获取本次 I/O 结果

之前钓鱼, 都是自己在操作, 但是现在我雇佣一个人来帮我钓鱼, 当他钓鱼结束后, 将钓到的鱼交给我就行, 我不用去管钓鱼的事.

当然这一种模型也很复杂的. 对于程序员的要求较高

同步和异步

同步: 必须等待任务完成后, 才能执行下一个任务. 像上面的四种 I/O 模型中, 无论是阻塞还是非阻塞, 我们都在等待它的返回值 (等待它执行的结果). 只有等到了它执行的结果, 程序才会继续向下执行. 异步也是不断地继续轮询, 直到等到执行的结果

异步: 则不会等待这个任务, 而是继续向后执行. 如异步的进行 I/O 请求, 虽然也会直接进行返回, 但是这个返回并没有附带本次操作的结果. 重点并不是返回, 重点是本次请求的结果是否被等待了. 至于本次任务执行的结果, 则是通过其他方法通知给程序 (如: 回调函数等)


http://www.kler.cn/a/516037.html

相关文章:

  • Spring MVC:综合练习 - 深刻理解前后端交互过程
  • PaSa - 大型语言模型提供支持的高级论文搜索代理
  • 使用KNN实现对鸢尾花数据集或者自定义数据集的的预测
  • 基于JAVA的微信点餐小程序设计与实现(LW+源码+讲解)
  • FCA-FineReport试卷
  • 数据挖掘常用算法模型简介
  • 有关Android Studio的安装与配置并实现helloworld(有jdk的安装与配置)(保姆级教程)
  • 云计算和服务器
  • 软件工程的本质特征
  • 无人机高速无刷动力电机核心设计技术
  • Python 之 Excel 表格常用操作
  • 考研机试:学分绩点
  • linux 扩容
  • MySQL 中开启二进制日志(Binlog)
  • 0164__【GNU】gcc -O编译选项 -Og -O0 -O1 -O2 -O3 -Os
  • three.js+WebGL踩坑经验合集(1):THREE.Line无故消失的元凶
  • c++-------------------------继承
  • 神经网络梯度爆炸的原因及解决方案
  • 10个非常基础的 Javascript 问题
  • Seata进阶全文详解(集成Nacos及SpringCloud配置)