【linux】五种IO模型与非阻塞IO

news2024/12/26 23:20:30

文章目录

  • 一、IO的概念
  • 二、IO的五种模型
    • 2.1 概念
    • 2.2 对比五种IO
  • 三、非阻塞IO
    • 3.1 fcntl文件描述符控制
    • 3.2 以非阻塞轮询方式读取标准输入

一、IO的概念

前面我们说过其实IO就是拷贝数据。
先说一下读取的接口:

当系统调用read/recv的时候会有两种情况
①没有数据,阻塞等待。
②有数据,read/recv拷贝完成后返回。

阻塞的本质就是等待资源(缓冲区)就绪。而且写数据也需要的等待(发送缓冲区被写满)。

由此得出IO不仅仅是拷贝数据:
IO = 等待资源就绪 + 拷贝数据

而我们说的IO效率低并不是拷贝的效率低,而是等的时间长。
所以有一个概念叫做高效IO,它的本质就是减少等待的时间(等待的比重)。

二、IO的五种模型

2.1 概念

先举个例子:

现在有几个人在钓鱼:
张三下勾后就一直死盯着鱼鳔,什么都不做,等待着鱼上钩。
李四下勾后一会看看书一会看看鱼鳔一会玩玩手机。
王五在钓竿上挂了个铃铛,下勾后就做自己的事情,铃铛响了头也不抬就钓上了鱼。
赵六有很多鱼竿,全部下勾后就一直遍历看是否有鱼上钩。
田七自己不钓,让别人钓,钓完后通知田七即可,田七最后直接获得鱼。

作为旁观者,我们认为只要一个人等待的时间少那么他的钓鱼效率就高。
由此判断赵六的效率最高,因为他的鱼竿多,鱼上钩的概率大,等待的时间就少。

把上述场景类比到计算机:

张三:阻塞IO
李四:非阻塞IO
王五:信号驱动IO
赵六:多路转接/复用
田七:异步IO
这几个人就相当于进程,田七雇佣的人就是操作系统,鱼就是数据,鱼塘就是内核空间,鱼鳔就是数据就绪的事件,鱼竿就是文件描述符,钓鱼的整个动作就是read/recv调用

  • 阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式。
  • 非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。

非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询, 这对CPU来说是较大的浪费,一般只有特定场景下才使用

  • 信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。
  • IO多路转接: 虽然看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。

进程受阻于select调用,select只负责等(无拷贝能力),当文件描述符就绪时(select返回时),用其他的IO类接口完成拷贝。

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

任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少

2.2 对比五种IO

  • 阻塞IO、非阻塞IO、信号驱动IO的对比

阻塞IO、非阻塞IO、信号驱动IO它们三个在IO上效率上没有区别(只有一个鱼竿)。
那在其他方面呢?在其他方面 非阻塞IO、信号驱动IO可以做更多的事情。
而阻塞IO和非阻塞IO"钓鱼"是一样的,不同的是等待的方式

  • 阻塞IO与非阻塞IO的对比

阻塞IO当数据资源没有准备好的时候会把进程放到等待队列中挂起,得到结果后才能返回。
而非阻塞IO当数据资源没有准备好的时候会直接返回(得知了数据资源没准备好)。

  • 信号驱动IO有没有等待?

等了,只不过等待的方式不一样。

  • 同步IO与异步IO

除了异步IO,其他几种IO都是进程自己参与了IO的过程(钓 + 等),所以称为同步IO
而因为田七没有参与IO的任何一个阶段,所以称作异步IO
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果。
异步则是相反, 调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后, 被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

  • 为什么多路复用IO高效?

因为减少了等待的比重

三、非阻塞IO

打开文件时默认都是以阻塞的方式打开的,如果要以非阻塞的方式打开某个文件,需要在使用open函数打开文件时携带O_NONBLOCKO_NDELAY选项,此时就能够以非阻塞的方式打开文件。

3.1 fcntl文件描述符控制

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

int fcntl(int fd, int cmd, ... /* arg */ );

fcntl函数的作用是对文件描述符进行控制操作。它可以实现文件锁定、非阻塞I/O、修改文件状态标志等功能。

参数说明:

fd:已经打开的文件描述符。
cmd:需要进行的操作。
:可变参数,传入的cmd值不同,后面追加的参数也不同。

fcntl函数常用的5种功能与其对应的cmd取值如下:

复制一个现有的描述符(cmd=F_DUPFD)。
获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)。
获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)。
获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)。
获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)。

  • 具体实现非阻塞流程

先调用fcntl函数获取该文件描述符对应的文件状态标记(这是一个位图),此时调用fcntl函数时传入的cmd值为F_GETFL
获取到的文件状态标记上添加非阻塞标记O_NONBLOCK设置回去。

void setNoBlock(int fd) 
{ 
	 int fl = fcntl(fd, F_GETFL); 
	 if (fl < 0) 
	 { 
	 	perror("fcntl");
	 	return; 
	 }
	 fcntl(fd, F_SETFL, fl | O_NONBLOCK); 
}

3.2 以非阻塞轮询方式读取标准输入

先来看看阻塞式输入的情况:

int main()
{
	char buf[1024];
	while(1)
    {
        std::cout << "[input]# ";
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof buf - 1);
        if(s > 0)
        {
            // 正常读取
            buf[s] = '\0';
            std::cout << "[echo]# " << buf << std::endl;
        }
        else if(s == 0)
        {
            // 输入完了
            std::cout << "read end" << std::endl;
            break;
        }
        else
        {
            // -1
        }
    }
	return 0;
}

在这里插入图片描述

可以看到如果我们没输入,它就会阻塞等待。
输入[Ctrl + d]就表示输入结束:
在这里插入图片描述


接下来看看非阻塞

bool setNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
	if (fl < 0)
    {
		std::cerr << "fcntl: " << strerror(errno) << std::endl;
		return false;
	}
	fcntl(fd, F_SETFL, fl | O_NONBLOCK);
	return true;
}

int main()
{
    setNonBlock(0);
	char buf[1024];
	while(1)
    {
        std::cout << "[input]# ";
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof buf - 1);
        if(s > 0)
        {
            // 正常读取
            buf[s] = '\0';
            std::cout << "[echo]# " << buf << std::endl;
        }
        else if(s == 0)
        {
            // 输入完了
            std::cout << "read end" << std::endl;
            break;
        }
        else
        {
            // -1
        }
        sleep(1);
    }
	return 0;
}

在这里插入图片描述
可以看到一个现象就是我输入我的,它打印它的。

所以我们可以在不输入的时候执行其他任务

typedef std::function<void()> func_t;

void TaskA()
{
    std::cout << "TaskA" << std::endl;
}

void TaskB()
{
    std::cout << "TaskB" << std::endl;
}

void TaskC()
{
    std::cout << "TaskC" << std::endl;
}

void ExecOther(std::vector<func_t>& v)
{
    for(auto& func : v)
    {
        func();
    }
}

int main()
{
    std::vector<func_t> cbs;// 回调方法
    cbs.push_back(TaskA);
    cbs.push_back(TaskB);
    cbs.push_back(TaskC);
    setNonBlock(0);
	char buf[1024];
	while(1)
    {
        std::cout << "[input]# ";
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof buf - 1);
        if(s > 0)
        {
            // 正常读取
            buf[s] = '\0';
            std::cout << "[echo]# " << buf << std::endl;
        }
        else if(s == 0)
        {
            // 输入完了
            std::cout << "read end" << std::endl;
            break;
        }
        else
        {
            // -1
        }
        // 执行其他任务
        ExecOther(cbs);
        sleep(1);
    }
	return 0;
}

在这里插入图片描述

  • 当read返回值是-1时如何区分是错误还是底层没有数据?

观察上面的代码,read出错和底层没有数据都会返回-1,那么怎么区分它们呢?
通过错误码

else
{
    // -1
    std::cout << "errno: " << strerror(errno) << std::endl;
}

在这里插入图片描述
表示资源没有准备好。、

当read函数以非阻塞方式读取标准输入时,当底层数据不就绪时,read函数是以出错的形式返回的,此时的错误码会被设置为EAGAINEWOULDBLOCK

此外,调用read函数在读取到数据之前可能会被其他信号中断,此时read函数也会以出错的形式返回,此时的错误码会被设置为EINTR,此时应该重新执行read函数进行数据的读取

int main()
{
    std::vector<func_t> cbs;// 回调方法
    cbs.push_back(TaskA);
    cbs.push_back(TaskB);
    cbs.push_back(TaskC);
    setNonBlock(0);
	char buf[1024];
	while(1)
    {
        std::cout << "[input]# ";
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof buf - 1);
        if(s > 0)
        {
            // 正常读取
            buf[s] = '\0';
            std::cout << "[echo]# " << buf << std::endl;
        }
        else if(s == 0)
        {
            // 输入完了
            std::cout << "read end" << std::endl;
            break;
        }
        else
        {
            // -1
            if(errno == EAGAIN)
            {
                // 底层没有数据
                // 执行其他任务
                ExecOther(cbs);
            }
            else if(errno == EINTR)
            {
                // 被信号中断
                continue;
            }
            else
            {
                std::cout << "errno: " << strerror(errno) << std::endl;
                break;
            }
        }
        sleep(1);
    }
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/779150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】指针进阶(3)

目录 指针和数组笔试题解析 一维数组 字符数组 二维数组 指针笔试题 在前面两篇文章&#xff0c;我们已经学完了指针进阶的所有知识点。在这篇文章中&#xff0c;我们主要学习的是一些常见的笔试题的总结。 指针和数组笔试题解析 在做题之前&#xff0c;我们先复习一下之…

第三讲:k8s核心概念和专业术语

序言&#xff1a;这里只对概念继续基础阐述&#xff0c;不做具体案例&#xff0c;这位博主写的特别详细&#xff0c;想要对k8s深入的了解可以跳转了&#xff0c;作为小白的我看的有点懵&#xff0c;毕竟没实践过 链接地址→ http://t.csdn.cn/ZYtEF 这篇文章写了将近两万字对各…

mybatis-plus 缓存深入实践(二)

mybatis-plus 缓存&#xff08;一&#xff09;回顾、缓存&#xff08;二&#xff09;深入实践

3D测量之圆柱轴线直线度测量

视频演示效果 圆柱轴线直线度测量 零、效果图 一、目标 测量圆柱轴线的直线度误差&#xff1b; 二、测量方法–轴截面法[1] 本文主要是通过最小二乘法确定各截面中心坐标值。由各截面测得的实际中心构成测得中心线。按误差评定方法进行数据处理&#xff0c;求出轴线的直线度误…

启动es容器错误

说明&#xff1a;启动es容器&#xff0c;刚启动就停止&#xff0c;查看日志&#xff0c;出现以下错误信息&#xff08;java.lang.IllegalArgumentException: Plugin [analysis-ik] was built for Elasticsearch version 8.8.2 but version 7.12.1 is running&#xff09; 解决&…

【状态估计】基于UKF、AUKF的电力系统负荷存在突变时的三相状态估计研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码及数据 &#x1f4a5;1 概述 基于UKF和AUKF的电力系统负荷存在突变时的三相状态估计研究是一种利用无迹卡尔曼滤波&#xff08;Unscented Kalman Filter, UKF&#xff09…

学习Dart语言---2023-07-23

环境搭建---windows Dart for WindowsDart installer for Windows. Installs the latest Dart SDK and Dartium.https://gekorm.com/dart-windows/选择标准版&#xff0c;下载安装&#xff0c;一直next 验证安装成功&#xff1a; IDEA中配置dart SDK 下载dart插件 创建dart文…

用Python脚本自动采集金融网站当天发布的免费报告

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 其间旦暮闻何物&#xff1f;杜鹃啼血猿哀鸣。 大家好&#xff0c;我是皮皮。 一、前言 前几天在Python群【林生】问了一个Python数据采集的问题&#x…

PCL点云处理之最小二乘直线拟合(2D| 方法2)(❤亲测可用❤)(二百零一)

PCL点云处理之最小二乘直线拟合(2D| 方法2)(❤亲测可用❤)(二百零一) 一、算法简介二、算法实现1.代码2.结果一、算法简介 在二百章中,我们介绍了一种最小二乘拟合直线点云(2D)的方法,可以获取直线方程系数k,b,这里介绍另一种拟合直线点云的方法,更为简单方便,结果…

引入第三方字体库 第三方字体库Google Fonts

googlefonts官方网站 googlefonts中国网站 本人是在微信小程序中引入 在static中建一个文件夹font-family 例如字体链接&#xff1a;https://fonts.font.im/css?familyKirangHaerang 将该链接的返回的资源的复制到css文件中 font-family.css /* [0] */ font-face {font-fam…

Linux学习之Ubuntu 20.04安装内核模块

参考博客&#xff1a;Ubuntu20.04编译内核教程 sudo lsb_release -a可以看到我当前的系统是Ubuntu 20.04.4&#xff0c;sudo uname -r可以看到我的系统内核版本是5.4.0-100-generic。 sudo apt-get install -y libncurses5-dev flex bison libssl-dev安装所需要的依赖。 su…

国密SSL优势及应用场景

国密SSL的优势主要有以下几点&#xff1a; 更高的安全性&#xff1a;国密算法采用的是国家密码管理局推荐的算法&#xff0c;相对于传统的SSL协议更加安全。 更好的性能&#xff1a;国密算法是国家密码管理局推荐的算法&#xff0c;其加密效率与密钥长度相比传统算法更高。 更…

Java集合之List

ArrayLsit集合 ArrayList集合的特点 ArrayList集合的一些方法 ①.add(Object element) 向列表的尾部添加指定的元素。 ②.size() 返回列表中的元素个数。 ③.get(int index) 返回列表中指定位置的元素&#xff0c;index从0开始。 public class Test {public static void m…

【Python学习笔记】记载解决Python报错HTTP Error 403: Forbidden的一波三折过程

【Python学习笔记】记载解决Python报错HTTP Error 403: Forbidden的一波三折过程 当前进度&#xff1a;还没有解决&#xff0c;但是已经尝试了好几种办法&#xff0c;此处做个记录&#xff0c;也许能帮上忙。 本帖是整理回顾帖&#xff0c;不是教程帖&#xff0c;追求一个完美…

QT日志调试系统(前台、后台、文件查看调试信息)

通过qInstallMessageHandler获取Qt的打印信息&#xff0c;将这些打印信息存放到一个Widget中&#xff0c;实现不通过后台就能查看日志信息。 实现方法如下&#xff1a; main.cpp #include "mainwidget.h" #include <QApplication> #include <QStyleFactory…

yum的安装和使用(包含安装过程中遇到的问题及解决方法)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

2022年十月份电赛OpenMV巡线方案详细代码分析(1)

前言 &#xff08;1&#xff09;马上要进行电赛了&#xff0c;机器识别是铁定会使用到的。为了防止出现去年十月份那种特殊的巡线方案。我在此分享出OpenMV巡线方案&#xff0c;并且进行讲解和分析如何更改。 &#xff08;2&#xff09;学习本文之前&#xff0c;需要学习&#…

通过nexus3部署公司内部的私有npm仓库

简介&#xff1a; 登录时使用默认用户admin&#xff0c;密码不知道就需要找默认的&#xff0c;点击Sign in时会提示你路径&#xff0c;这里我是这样查的&#xff0c;在linux服务器上输入以下命令 ​编辑 前言&#xff1a; 准备工作&#xff0c;可能需要一台linux服务器&#x…

Jenkins报警机制的配置与Linux的使用总结

先在钉钉中添加一个机器人 在Configure System中找到机器人选项&#xff0c;并且复制webhook到网络钩子&#xff0c;然后添加机器人的编号、名称和关键词&#xff0c;然后点击测试&#xff0c;如果显示测试成功则表示配置成功&#xff0c;最后保存 再到配置中勾选顶顶机器人的定…

19.matlab数据分析插值(matlab程序)

1.简述 数据插值的计算机制 数据插值是一种函数逼近的方法。 一维插值 Y1interp1(X,Y,X1,method) 二维插值 interp2():二维插值函数。 调用格式: Z1interp2(X,Y,Z,X1,Y1,method) 其中&#xff0c;X、Y是两个向量&#xff0c;表示两个参数的采样点, Z是采样点对应的函数值。X1…