高级IO——五种IO模型

一般我们在写一些简单的小项目的时候,免不了会用到IO接口,比如C语言中的scanf/printf又或者是
C++中的cout/cin,或者是在Linux操作系统中的文件IO接口read/write。这些接口默认都是阻塞的,
这又引出了阻塞/非阻塞IO的概念,由此可见IO是我们在进行代码编写时不可或缺的一个模块,其实
关于IO不仅仅有阻塞/非阻塞的IO方式,还有着其它类型的IO模式,那么从这一篇文章开始,我将会
介绍不同模式的IO,以便让我们在不同项目环境下编写出更好的代码。

五种IO模型

  接下来我们先简单的认识一下五种IO模型。

a. 阻塞IO

  阻塞IO是我们最经常使用的一种IO方式,比如C语言中的scanf、C++中的cin、Linux中的read、recv。这些都是阻塞式的读入输入缓冲区中的内容到进程中,而它们阻塞的原因也很简单那就是此时这些接口要读取的输入缓冲区没有数据而导致该执行流获取不到资源而被阻塞,同时一些输出接口也会遇到这种问题,但是输出接口阻塞的原因是因为输出接口要输出的目标缓冲区满了,此时不能再向该缓冲区中输入数据,执行流也会被阻塞。但是输出缓冲区满了的情况是很少的,主要还是输入缓冲区中没有数据导致执行流 以阻塞式读取该缓冲区数据时,执行流被阻塞。

b. 非阻塞IO

  关于非阻塞IO,有些使用C语言或者C++再Windows上写过一些单线程的小游戏,比如贪吃蛇、俄罗斯方块等这些都是需要非阻塞IO的存在,因为当游戏开始的时候就算用户没有输入,游戏也会正常运行,也就是让程序正常执行,这样的场景下,非阻塞式的IO就是必要的。非阻塞IO不像阻塞IO一样在使用IO接口时,如果读写条件不就绪(输入缓冲区为空或者输出缓冲区为满)执行流就会被阻塞,此时非阻塞IO接口会直接返回,并且继续向后执行代码,这样当我们实现一个小游戏的时候就算玩家没有使用键盘或者鼠标进行任何操作,游戏也会正常执行之后的代码,使游戏正常运行。
  关于C语言中在Windows下有一个非阻塞IO的接口:
头文件是 conio.h,接口是_getch,是一个非阻塞的字符输入接口。
  而在Linux中,设置非阻塞的方式有多种,我们也主要认识Linux中的非阻塞设置方式。

Linux中的非阻塞

  使用过Linux中的关于文件描述符的IO接口都知道,这些IO接口一般在传参的时候,会让我们提供一个标志位:
在这里插入图片描述
  而这个标志位的本质其实就是一个位图,众多标志位中,有一个功能就是设置非阻塞:
在这里插入图片描述
  这两个标记位都可以实现非阻塞的功能,但是使用的场景有所不同,MSG_DONTWAIT是 使用在一些IO接口上,如:recv,recvfrom等。O_NONBLOCK则是在文件打开或者创建套接字文件时使用的标志位,非阻塞作用在文件描述符上。
在有了上面的认识之后我们再来认识一个将一个打开的文件描述符设置为非阻塞的接口。

fcntl

在这里插入图片描述
  这个系统调用可以对一个文件描述符进行某些操作,其中有一点就是将一个文件描述符设置为非阻塞,使用的方式也很简单:
  首先我们先来认识一下使用阻塞IO时的情况:
在这里插入图片描述

在这里插入图片描述
  可以看到 ,当我们不使用键盘进行输入操作时,进程就会被阻塞。
  现在我们来实现非阻塞IO:
在这里插入图片描述
  这就是将一个文件描述符设置为非阻塞的流程:首先我们先使用F_GETFL获取到文件描述符上记录该文件描述符特点的标志位,之后我们将这个标志位按位或上O_NONBLOCK,再将它设置进文件描述符的标志位中,这样该文件描述符不论被哪个IO接口使用都是非阻塞状态了,接下来我们在来体验一下对非阻塞的文件描述符进行读取数据是什么样的感觉:
在这里插入图片描述
关于非阻塞我们知道当读取数据时如果有数据就会读取数据,如果没有数据也会立即返回,所以我们的代码可以是这样,同时我们也来观察输入缓冲区当没有数据时read的返回值:
在这里插入图片描述
在这里插入图片描述
  这时候我们知道,当以非阻塞的方式使用read对文件描述符进行读取数据输入缓冲区没有数据,那么read的返回值是-1,这里可能有人就会有疑问了read的返回值-1不是代表读取出错了吗,怎么又会是没有数据呢?这个问题我们稍后再谈,我们先来规范一下上面关于读文件时的过程,在上面的代码中我们只分析了输入缓冲区有数据,然后就直接else了,这样的处理方式是不对的,加入了解管道或者套接字通信的会知道当不需要再读(对端关闭)的时候,使用read继续对该文件描述符读取,此时read的返回值是0,所以我们的代码应该是下面这样:
在这里插入图片描述
  在运行起程序的时候我们,可以使用ctrl + D(表示向进程发送结束输入的信号)的方式来演示出对端关闭的场景:
在这里插入图片描述

read的返回值

  在上面使用非阻塞方式读取数据的时候我们发现读取数据出现错误与读取数据时输入缓冲区没有数据的返回值都是-1,难道两种是一种情况吗?显然不是的,所以我们再次阅读read的返回值我们就会发现这两者的不同:
在这里插入图片描述
  在文档的介绍中,虽然非阻塞读取数据出入缓冲区没有数据的行为被认定为是read读取出现错误,但是同时错误码也会被设置,错误码标示着read出错的错误原因,而我们需要关注其中几个错误码:

EAGAIN:用于指示操作目前无法完成,但可以在将来的某个时刻再次尝试。IO时就是在上述非阻塞读取输入缓冲区
没有数据时错误码就会被设置为EAGAIN。
EWOULDBLOCK:跟EAGAIN其实是一个意思,查看代码我们会发向它俩是一个东西。
EINTR:这个错误码表示,当执行流正在读取数据时突然被信号中断了,那么错误码就会被设置为EINTR。

  在了解过上面相关的错误码之后我们就知道了,其实read返回值为-1之后我们可以进一步通过错误码来判断返回值为-1的原因,并根据这个原因做出相应的处理方式,那么我们最终的结果代码 就出来了:
在这里插入图片描述
  这也就是Linux中非阻塞的读取文件的方式。

c. 信号驱动IO

  对Linux中的信号有过了解的话就会知道,当操作系统向进程发送信号的时候,该进程就会直该信号对应的信号处理函数,并且我们也可以使用signal函数来让进程在处理某个信号时,使用我们的自定义方法。
  通过这个机制我们在自定义信号处理函数中进行IO,这样当IO条件就绪时,就让操作系统向该进程发送信号,从而让该进程进行IO,这种就是信号驱动式的IO。

d. 多路转接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时间是否就绪
  这样说来多路转接接口的优化等的方式并不是从单个文件描述符来说的,而是在面对大量的文件描述符IO的时候,多路转接接口将这些文件描述符等的时间进行了重叠,这样的话也算是优化了等的时间。
  此时 有人就有疑问了,同时检测多个文件描述符IO条件是否就绪,如果就绪的我们就发,不就绪就不发,这样的功能我们也可以自己实现啊比如使用非阻塞式IO + 一次遍历所有的文件描述符进行IO。
  这样的想法虽然确实可以实现等待时间重叠的效果,但是仍然会有很大的缺陷,首先从效率上来说由于我们处于用户层,而IO条件就绪的本质 是在内核层,这样的话我们对于IO条件是否就绪的判断一定不如内核去判断某个文件描述符是否可以IO的效率更高(最明显的就是用户态和内核态之间需要进行切换)。
  其次 从编程角度来说 ,倘若使用非阻塞IO进行遍历文件描述符进行IO时,如果某个文件描述符不具备IO条件,那我们可能会对特定的文件描述符进行特定的操作,这增加了代码的复杂性 。
  最后关于多路转接的底层,其实也并不一定是对遍历所有需要 关心IO条件是否就绪的文件描述符进行遍历 操作式的判断,多路转接会进行更高效率的判断某个文件描述符的IO条件是否就绪,这也是上述方式不可行的原因。
  而在之后的过程中,我也会介绍多路转接式的IO如何去进行编写。

e. 异步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方式归类为同步IO。有人就会自然而然地将执行流的同步和异步类比到IO中,首先说明:直接将两者混为一谈是不对的(场景不同),但是两者在思想上具有相似性。
  对于同步而言不论是IO还是执行流,它们都体现了很强的顺序性,那就是我必须干完这件事之后再干其他事(比如IO需求提出后必须得到IO结果再干其他事,一个执行流要继续向后执行也必须等待某一个执行流执行到一定程度在开始执行),而异步也是一样,在提出IO需求之后不需要得到IO结果可以继续干其他事,执行流的执行也不需要其他执行流执行到某一程度再开始执行。
这就是我对五种IO模型的大致理解,如果有错误的地方,希望指正。

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

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

相关文章

白平衡之White Patch Algorithm

免责声明:本文所提供的信息和内容仅供参考。作者对本文内容的准确性、完整性、及时性或适用性不作任何明示或暗示的保证。在任何情况下,作者不对因使用本文内容而导致的任何直接或间接损失承担责任,包括但不限于数据丢失、业务中断或其他经济损失。 读者在使用本文信息时,应…

Lobby——网络游戏大厅设计与参考建议!!!

随着网络游戏越来越多,游戏的主界面也是做的越来越花哨,各种界面层出不穷!恨不得,一个主界面直接把所有的业务塞满!! 看着这十年不换的界面,经久不换,如同嚼蜡!你会发现x…

GPU编程(1)GPU架构

总体 显卡结构 风扇在下面,采用热管方式,用气体液体的转化来带走热量。包裹热管的是铜制散热板,外围是铝制格扇,更快排除热量。 视频接口个pcie都是直接连接GPU。 所有的供电模块公用一个PWM芯片。 显存的型号就称之为显存颗粒…

保护企业终端安全,天锐DLP帮助企业智能管控终端资产

为有效预防员工非法调包公司的软硬件终端资产,企业管理员必须建立高效的企业终端安全管控机制,确保能够即时洞察并确认公司所有软硬件资产的状态变化。这要求企业要有一套能够全面管理终端资产的管理系统,确保任何未经授权的资产变动都能被迅…

Git推送被拒

今天开发完成一个新的需求,将自己的分支合并到test分支后,推送到远程仓库,结果显示推送被拒: 原因是因为有人更新了test分支的代码,我在合并之前没有拉取最新的test分支代码,所以他提示我“推送前需要合并…

企业级业务架构和IT架构规划方案(120页PPT下载)

方案内容综述 方案涵盖了从战略分析到具体实施路径的内容。提出了IT架构规划的工作思路,包括项目启动、部门访谈、资料收集、内部数据库搜索与先进实践研究等步骤,旨在通过这些步骤完成现状及差距分析,并基于此设计未来的应用架构、数据架构…

面向对象基础-继承

1.继承定义 继承是一种**“is-a”**(“是一个”)关系,它表示一个类是另一个类的特殊化版本。 2.继承作用 通过继承,子类能够获取父类的属性和方法,并且可以对这些属性和方法进行增强、扩展或重写 3.继承特点 3.1子…

计算机网络——应用层(DNS域名系统、文件传输协议FTP、远程终端协议TELNET、万维网)

应用层概述 不同网络应用的应用进程之间,还需要用不同的通信规则。因此在运输层协议之上,还需要有应用层协议。 每个应用层协议都是为了解决某一类应用问题,而问题的解决又必须通过位于不同主机中的多个应用进程之间的通信和协同工作来完成。…

C++算法练习-day2——27.移除元素

题目来源:. - 力扣(LeetCode) 题目思路分析 给定一个有序数组和一个目标值,题目要求从数组中移除所有等于目标值的元素,并返回移除后数组的新长度。注意,题目要求原地修改数组,即不使用额外的…

YoloV8改进:Block改进|使用ContextAggregation模块改善C2f模块|即插即用

摘要 在计算机视觉领域,目标检测与实例分割任务一直是研究的热点。YoloV8作为目标检测领域的佼佼者,凭借其出色的性能和效率赢得了广泛的认可。然而,随着技术的不断进步,如何进一步提升YoloV8的性能成为了我们追求的目标。近期,我们引入了ContextAggregation模块对YoloV8…

信息安全工程师(53)网络安全审计机制与实现技术

前言 网络安全审计机制是指为了保护网络安全并发现潜在风险和漏洞而进行的一系列审计活动。审计的目的是检查并评估网络系统的安全性,以确保其符合相关法律法规和安全标准。 一、网络安全审计机制的重要性 网络安全审计机制对于保护组织的信息资产和敏感数据至关重要…

LabVIEW提高开发效率技巧----高效文件I/O

在LabVIEW开发中,文件I/O操作常常是性能瓶颈之一,特别是处理大数据时,如何高效地存储和读取数据显得尤为重要。本文将详细介绍如何利用TDMS Streaming来实现高效的文件I/O,并结合具体例子说明在实际开发中的应用技巧。 1. 什么是T…

[漏洞挖掘与防护] 04.Windows系统安全缺陷之5次Shift漏洞启动计算机机理分析

这是作者新开的一个专栏——“漏洞挖掘与防护”,前期会复现各种经典和最新漏洞,并总结防护技巧;后期尝试从零学习漏洞挖掘技术,包括Web漏洞和二进制及IOT相关漏洞,以及Fuzzing技术。新的征程,新的开启,漫漫长征路,偏向虎山行。享受过程,感谢您的陪伴,一起加油~ 欢迎关…

迅为RK3562开发板/核心板240PIN引脚全部引出,产品升级自如

可应用于人脸跟踪、身体跟踪、视频监控、自动语音识别(ASR)、图像分类驾驶员辅助系统(ADAS)、车牌识别、物体识别等。 iTOP-3562开发板/核心板采用瑞芯微RK3562处理器,内部集成了四核A53Mali G52架构,主频2GHZ,内置1TOPSNPU算力,R…

Gin框架操作指南08:日志与安全

官方文档地址(中文):https://gin-gonic.com/zh-cn/docs/ 注:本教程采用工作区机制,所以一个项目下载了Gin框架,其余项目就无需重复下载,想了解的读者可阅读第一节:Gin操作指南&#…

Redis Geo 数据类型解析:基于 ZSET 的高效地理位置管理0708

根据官网介绍: Bitmaps are not an actual data type, but a set of bit-oriented operations defined on the String type which is treated like a bit vector. Since strings are binary safe blobs and their maximum length is 512 MB, they are suitable to s…

【回顾一下AQS知识,关于公平锁与非公平锁】

文章目录 一.什么是AQS二.公平锁和非公平锁实现三.公平锁和非公平锁的区别四.小结 一.什么是AQS AQS,全称 AbstractQueuedSynchronizer,是 Java 中用于构建锁和同步器的一个基础框架类,位于 java.util.concurrent.locks 包中。AQS 通过一个先…

Android 15 推出新安全功能以保护敏感数据

Android 15 带来了增强的安全功能,可保护您的敏感健康、财务和个人数据免遭盗窃和欺诈。 它还为大屏幕设备带来了生产力改进,并对相机、消息和密钥等应用进行了更新。 Android 防盗保护 Google 开发并严格测试了一套全面的功能,以在盗窃之…

Java基础(6)

深拷贝和浅拷贝区别了解吗?什么是引用拷贝?关于深拷贝和浅拷贝区别,我这里先给结论:浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部…

谷歌浏览器如何展示小于12px的字

在谷歌浏览器里面&#xff0c;字体最小只能设置为12px&#xff0c;但是我们有时候需要展示更小的文字。方法如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" co…