一、五种 IO 模型

1、简要理解

这里以一个钓鱼的例子来说明五种IO模型:

  • 张三:专注钓鱼,鱼漂不动,张三不动 — 阻塞 IO
  • 李四:不会因为鱼没有上钩而卡在检测鱼漂上 ---- 非阻塞 IO
  • 王五:通过让鱼上钩,反向通知我 — 信号驱动 IO
  • 赵六:拉了一卡车的鱼竿 (100 只鱼竿) ---- 多路复用,多路转接
  • 田七:我是喜欢吃鱼!发起钓鱼,让小王去钓鱼 — 异步 IO

问题 1:阻塞 vs 非阻塞

  • 阻塞:IO 条件不具备时,一直卡住,直到条件就绪。
  • 非阻塞:IO 条件不具备时,立即出错返回,不阻塞等待。
  • 核心差异:等待方式不同,非阻塞可同时做其他事。

问题 2:谁的钓鱼效率最高?
多路复用(赵六)效率最高,因为单位时间内等待的占比最低。

问题 3:信号驱动 IO(王五)有没有等待?
有参与钓鱼(IO)流程,只是不用主动检测条件。

结论 4:同步 IO 模型
阻塞、非阻塞、信号驱动、多路复用,都属于同步 IO
只要用户参与了「等待」或「拷贝」任一阶段,就是同步 IO。

问题 5:同步 IO vs 异步 IO

  • 同步 IO:用户参与「等待」或「拷贝」任一阶段。
  • 异步 IO:用户只发起 IO,等待和拷贝全由内核完成,与用户流程无关。

2、阻塞 IO

核心:IO 分等待数据和拷贝数据两步,不同模型的差异主要在于等待数据

  • 流程:调用recvfrom后一直阻塞,直到数据准备好并拷贝完成才返回。
  • 特点:最简单,默认方式,但一个线程只能处理一个 IO,等待期间无法做其他事。

3、非阻塞 IO

  • 流程:调用recvfrom时数据没准备好会立刻返回EWOULDBLOCK错误,需要反复轮询直到成功。
  • 特点:不阻塞,但轮询会浪费 CPU 资源,一般很少直接用。

4、信号驱动 IO

  • 流程:提前注册SIGIO信号处理函数,数据准备好时内核发信号通知,再调用recvfrom拷贝数据。
  • 特点:等待期间进程可做其他事,但信号处理和多线程场景容易有竞态问题。

5、IO 多路转接(多路复用)

  • 流程:通过select/poll/epoll同时等待多个文件描述符,任意一个就绪后再调用recvfrom处理。
  • 特点:单线程可同时管理多个 IO,效率高,是高并发服务的主流方案。

6、异步 IO

  • 流程:调用aio_read后立刻返回,内核负责等待 + 拷贝数据,全程不阻塞,拷贝完成后发信号通知应用。
  • 特点:完全异步,效率最高,但实现复杂,依赖系统支持。

二、高级 IO 重要概念

1、同步通信 vs 异步通信

  • 同步通信

    • 定义:调用发出后,必须主动等待结果返回,调用不返回就一直阻塞,拿到结果才继续执行。
    • 特点:调用者全程等结果,逻辑简单但效率低。
  • 异步通信

    • 定义:调用发出后立刻返回,不阻塞等待结果;后续由被调用者通过 状态通知 / 回调函数,主动把结果反馈给调用者。
    • 特点:调用者可继续做其他事,效率高但逻辑更复杂。
  • 注意事项

    • IO / 通信场景:指同步 / 异步通信,描述的是消息通知机制
    • 多进程 / 线程场景: 指同步 / 互斥,描述的是多任务之间的执行顺序、资源访问制约关系,和前者完全无关。

2、阻塞 vs 非阻塞

  • 阻塞调用: 调用结果未返回前,当前线程会被挂起(无法执行其他操作),直到拿到结果才恢复运行。
  • 非阻塞调用: 即使暂时拿不到结果,调用也会立刻返回,线程不会被挂起,可继续执行其他操作(通常需要后续轮询获取结果)。

总结: 阻塞是 “调用时死等”,非阻塞是 “调用时不等,能继续做别的事”

3、高级 IO

除基础 IO 外,以下这些统称为高级 IO:

  • 非阻塞 IO
  • 记录锁
  • 系统 V 流机制
  • IO 多路转接(IO 多路复用,如 select/poll/epoll)
  • readv/writev 分散 - 聚集 IO
  • 存储映射 IO(mmap

4、非阻塞 IO

  • 文件描述符默认是阻塞 IO
  • 使用 fcntl() 函数可将其设置为非阻塞

1)fcntl 函数

2)实现函数 SetNoBlock

作用:把文件描述符设为非阻塞 IO

void SetNoBlock(int fd)
{
    // 1. 获取当前文件状态标记
    int f1 = fcntl(fd, F_GETFL);
    if(f1<0)
		return;
		
    // 2. 加上非阻塞标记, 重新设置
    fcntl(fd, F_SETFL, f1 | O_NONBLOCK);
}

3)轮询方式读取标准输入

#include <iostream>
#include <unistd.h> 
#include <fcntl.h>  

// 设置文件描述符为非阻塞
void SetNoBlock(int fd)
{
    int f1 = fcntl(fd, F_GETFL);
    if(f1<0)
        return;

    fcntl(fd, F_SETFL, f1 | O_NONBLOCK);
}

int main()
{

    SetNoBlock(0);  // 标准输入非阻塞
    char buffer[1024];

    while (true)
    {
        ssize_t n = read(0, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n - 1] = 0;
            std::cout << buffer << std::endl;
        }
        else if(n==0)
        {
            break;
        }
        else
        {
            // 无数据/被信号打断 → 继续轮询
            if (errno == EAGAIN || errno == EINTR)
            {
                std::cout << "数据未就绪..." << std::endl;
                sleep(1);
            }
            else 
            {
                break; // 真错误
            }
        }
    }
    return 0;
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐