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

五种IO模型

目录

一、阻塞IO

二、非阻塞IO

三、信号驱动IO

四、多路转接

五、异步IO

六、同步和异步的区分


        在前面的学习中,我们已经了解了网络通信的本质就是把数据拷贝到读写缓冲区中,然后由网卡接受/发送给对方,对方再从网卡中读取到数据的结果。但是我们在使用recv/send这样的接口的时候曾经说过,如果读写缓冲区没有就绪(读缓冲区没有数据/写缓冲区没有空间),程序会阻塞在该系统调用处。直到读写缓冲区中得到了资源,才会从阻塞状态重新工作。

        这里我们可以提炼一个概念:IO=等资源就绪+拷贝。

        所以如果一个服务器在运行的时候,有很多的客户端都和你建立了连接,但是却不发消息,让服务器阻塞在read等系统调用处,这样即使你的服务器采取了多线程多进程的模式,仍会很快就因为资源打满而卡死。但是你会发现,你服务器的性能降低的问题并不在于服务器本身的配置不够好,而是把大量的资源浪费在了IO的等待处!

        所以为了提高服务器的性能,往往会采取把等待时间的比重降低的策略,来把更多资源用于处理数据。下面我们提到的5种IO模型就是围绕IO中等待是否高效进行的。

一、阻塞IO

        阻塞IO就是我们最常使用的IO模型,在默认情况下所有的套接字都是采取的阻塞IO。阻塞IO即在操作系统将数据准备好之前,程序一直不往后执行。

显然,阻塞IO的效率非常低,因为他等待的占比太大了。

二、非阻塞IO

        非阻塞IO顾名思义,就是在底层资源没有就绪的时候,不会傻傻的等待,而是直接返回-1,并且将错误码设置为EWOULDBLOCK/EAGAIN (这是全局变量errno)。然后在下一次查看资源是否就绪的期间内,可以做其他事情。这种方式通常会搭配循环使用,我们称之为轮询

     

         因为所有的套接字默认创造出来的时候就是阻塞等待的,那么如何将其修改为非阻塞等待呢?我们可以使用fcntl接口。

        fcntl是File Control的缩写,是一个用于文件描述符的通用接口。它允许程序对文件描述符执行各种操作,包括获取和设置文件描述符的状态标志、文件锁定、文件描述符的复制等。在Unix和类Unix系统中,fcntl是一个非常重要的系统调用,广泛应用于文件操作、进程间通信、网络编程等领域。

        fcntl函数的一般形式如下:

其中cmd可以有以下几种:

通常是先get获取文件描述符的状态标志,然后再set设置文件描述符的状态标志。

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

using namespace std;

int main() {
    SetNonBlock(0);
    while (1) {
        char buffer[1024];
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0) {
            buffer[s] = 0;
            cout << buffer << endl;
        } else if (s == 0) {
            cout << "读到文件结尾了" << endl;
            break;
        }
        else
        {
            //1. 数据没用准备好 2. 真的出错了. 都以-1的返回值返回
            // 数据没有准备好,不算出错. 需要区分这两种情况
            if(errno == EWOULDBLOCK || errno == EAGAIN)
            {
                cout<<"os底层数据还没就绪"<<endl;
                cout<<errno<<endl;
            }
            //被信号中断, 也不算read出错
            else if(errno == EINTR)
            {
                cout<<"IO interrupted by signal"<<endl;
            }
            else
            {
                cout<<"read error"<<endl;
                break;
            }
        }
        sleep(1);
    }
}

       这种方式虽然相比较于阻塞IO,能把等待的时间利用起来了,但是由于需要频繁的轮询,往往会导致CPU不停的工作发热,由于硬件问题而使得性能下降,所以一般只会在特定的场景下使用。

三、信号驱动IO

        信号驱动 IO: 内核将数据准备好的时候, 使用 SIGIO 信号通知应用程序进行 IO操作。即在上面非阻塞的情况下把原本的定时监测,改为了由操作系统发现就绪后来给你的进程发信号,提醒你数据已经就绪了,而你在等待期间就不再需要频繁记得去检测,就能更好的完成其他代码逻辑。

四、多路转接

        IO 多路转接: 虽然从流程图上看起来和阻塞 IO 类似. 实际上最核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态。即同时有大量的套接字处于等待状态,而调用select则告诉操作系统我今天不想自己等待了,你去帮我等,只要有资源了,再来提醒我,这样就将许多等待的时间重叠了。

        这种方式往往是效率最高的。

五、异步IO

        异步 IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

        异步IO其实和之前4种方式都不太一样,前面无论是谁在帮你等,都是要自己处理数据的,但是异步IO则相当于甩手掌柜,只用告诉操作系统我要读写数据了,就走了。剩下的等待+拷贝操作都由操作系统自动完成。把所有事情做完了再通过信号告诉进程。

六、同步和异步的区分

同步和异步关注的是消息通信机制

(1)所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;
(2)异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

        异步和同步各有优势,同步更强调即时性,而异步通常适用于不需要立刻返回结果,可以在后台运行,所以往往异步的成文较低。我们在实际操作中要按照需求来选取。

相关文章:

  • 【JavaEE】Mybatis XML配置文件实现增删改查
  • 编程从键盘输入一个大写英文字符,将其转换为小写字符显示并显示出它的十进制,十六的 ASCI码。
  • Kubernetes集群中部署SonarQube服务
  • Gitee上库常用git命令
  • Babel 从入门到精通(四):@babel/template的应用实例与最佳实践
  • 【JavaEE】springMVC返回Http响应
  • 【负载均衡系列】Nginx
  • 【例6.5】活动选择(信息学奥赛一本通-1323)
  • 如何拆解模糊需求管理
  • 【C语言】自定义数据类型:联合体和枚举
  • Java Collection API增强功能系列之二 List.of、Set.of、Map.of
  • 《Python全栈开发》第12课:RESTful API设计 - 构建现代化接口
  • ArrayList<E>案例//定义一个方法,将价格低于3000的手机信息返回
  • 0324-项目
  • 【蓝桥杯速成】| 11.回溯 之 子集问题
  • SpringBoot星之语明星周边产品销售网站设计与实现
  • 内存管理模块设计与实现
  • 单片机的时钟输出功能-MCO输出(7)
  • Android 13深度定制:揭秘类MIUI全面屏手势返回动效的架构级实现
  • 零知识证明:区块链隐私保护的变革力量
  • 美政府被曝下令加强对格陵兰岛间谍活动,丹麦将召见美代办
  • “救护车”转运病人半路加价,从宝鸡到西安往返都要多收钱
  • 美联储主席:关税“远超预期”,美联储实现目标的进程或被推迟至明年
  • 习近平抵达莫斯科对俄罗斯进行国事访问并出席纪念苏联伟大卫国战争胜利80周年庆典
  • 安徽六安原市长潘东旭,已任省市场监督管理局党组书记、局长
  • 金沙记忆|元谋龙街渡:是起点也是终点