四种IO模型

本文详细解析了IO的基本原理,包括read和write系统调用过程,以及同步阻塞IO、同步非阻塞NIO、IO多路复用和异步IO四种模型的特点、优缺点。深入探讨了不同IO模型在多线程场景下的应用与效率问题。

小伙伴们在工作中是否经常听到系统瓶颈这个词,系统的瓶颈一般取决于系统属于IO密集型和CPU密集型,Java web项目中,一般系统的瓶颈都取决于IO而非CPU,CPU不是也不应该是Java项目的瓶颈所在;所以说,IO是比较重要的知识点,了解IO的原理和模型,对于我们开发大型项目是有必要的。

IO的基本原理

IO涉及到的主要有read和write两大系统调用,以write为例,一次write调用并非是直接将用户进程的数据直接传输到网卡等输出设备,而是操作系统将数据从用户进程的缓冲区复制到内核缓冲区,然后操作系统再将数据通过网卡等设备进行输出,read调用也同理。

所以说IO的过程主要分为两阶段,并且读写是相反的,read是先输入再复制,write是先复制再输出。

模型

  • 同步阻塞IO(Blocking IO)
    • 特点:两阶段都阻塞
    • 优点:简单,易于理解
    • 缺点:在多线程的场景下使用,一个线程对应一个连接,大量的线程频繁地切换将会降低系统的效率
  • 同步非阻塞NIO (None-Blocking IO)
    • 特点:在数据输入的时候不阻塞,在复制数据的时候阻塞,需要轮询直至数据就绪
    • 优点:相较于同步阻塞IO更加灵活,数据未就绪也可以做其他事情
    • 缺点:轮询需要占用大量的CPU时间,从而导致系统效率低下
  • IO多路复用(IO Multiplexing)
    • 特点: 采用了React反应器模式,Java的Selector和linux的epoll都是采用了这种模型。将连接注册到select,使用一个select查询线程来轮询内核,返回一个就绪的连接的列表,然后再进行复制,select调用和复制数据都是阻塞的。
    • 优点:一个线程便可以管理成千上万个连接,十分高效
    • 缺点:本质上是使用的阻塞的IO,阻塞便会使系统吞吐量降低
  • 异步IO
    • 向内核注册IO操作并调用,然后用户进程或线程就可以去处理其他事情,当数据就绪后,操作系统将数据复制到缓存区,然后向调用的进程发送信号或者执行注册的回调函数。
    • 优点:全程无阻塞,真正的异步
    • 缺点:需要操作系统内核的支持

写在最后

笔者是一名初出茅庐的码农,在此记录学习中的收获,如有错误,欢迎各位大佬进行批评指正

### 四种IO模型及其工作原理 #### 1. 阻塞IO模型 (Blocking IO Model) 在阻塞IO模型中,当应用程序发起一个读取操作时,如果数据尚未准备好,则调用会一直等待直到数据准备完毕并完成传输。在此期间,进程会被挂起,无法执行其他任务[^4]。 ```c int fd = open("file.txt", O_RDONLY); char buffer[256]; read(fd, buffer, sizeof(buffer)); // 如果没有数据可读,此函数将阻塞当前线程或进程 ``` 这种模型简单易实现,但在高并发场景下效率较低,因为每次都需要等到数据完全到达才能继续处理下一个请求。 --- #### 2. 非阻塞IO模型 (Non-blocking IO Model) 非阻塞IO通过设置文件描述符为`O_NONBLOCK`标志来避免长时间的等待状态。即使数据未就绪,系统也会立即返回错误码(通常是`EAGAIN`或者`EWOULDBLOCK`),允许程序快速轮询以检查是否有新数据到来[^2]。 然而频繁地查询设备可能导致CPU资源浪费严重,因此它并不适合用于实际生产环境中的大规模应用需求之上。 ```c int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); while(1){ int ret = read(fd, buf, BUF_SIZE); if(ret == -1 && errno == EAGAIN){ /* No data yet */ } } ``` 尽管如此,在某些特定情况下比如实时性要求较高的小型嵌入式项目里可能仍然会选择这种方式作为解决方案之一[^3]. --- #### 3. IO多路复用模型 (I/O Multiplexing Model) 利用select、poll以及epoll等机制可以同时监控多个文件句柄的状态变化情况而无需创建额外数量级相同的子线程/进程来进行单独管理;一旦某个被监视对象发生了预期事件就会通知到主循环从而做出相应反应动作. 这种方法显著提高了单个进程中能够有效控制的最大连接数上限,并且由于减少了上下文切换开销所以整体性能表现更好一些. 下面是一个简单的例子展示了如何使用 `epoll` 来监听 socket 连接: ```c #include <sys/epoll.h> #define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd; // 初始化 epoll 并注册感兴趣的事件... nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); for(int n=0; n<nfds; ++n){ if(events[n].data.fd == listen_sock){ accept_new_connection(); }else{ handle_data_from_conn_socket(); } } ``` 相比前两种方法而言,该技术更适合构建大型网络服务端软件架构体系结构之中去满足日益增长的数据交互频率趋势下的高效稳定运行目标追求方向上迈进了一大步距离之外还兼顾到了跨平台兼容性的考虑因素在里面呢! --- #### 4. 异步IO模型 (Asynchronous I/O Model) 真正的异步输入输出意味着提交了一个读写指令之后即可去做别的事情而不必关心何时结束甚至不需要主动询问其进度状况怎样样——由底层硬件设施自动完成后触发回调告知用户空间层面上的应用逻辑部分知晓结果信息反馈回来再做进一步后续安排处置措施行动起来吧[^5]! 例如 POSIX AIO 接口提供了这样的功能: ```c struct aiocb cb; memset(&cb, 0, sizeof(cb)); cb.aio_fildes = fd; cb.aio_buf = buffer; cb.aio_nbytes = size; aio_read(&cb); // 提交异步读取请求 /* 可以在这里执行其他任务 */ if(aio_error(&cb) == 0){ ssize_t result = aio_return(&cb); } else { perror("Error occurred"); } ``` 值得注意的是,现代操作系统通常会对AIO的支持有所差异,开发者需仔细查阅文档确认具体行为特性。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值