【Linux高级 I/O(1)】如何使用阻塞 I/O 与非阻塞 I/O?

news2024/10/7 18:23:30

        本系列再次回到文件 I/O 相关话题的讨论,将会介绍文件 I/O 当中的一些高级用法,以应对不同应用场合的需求,主要包括:非阻塞 I/O、I/O 多路复用、异步 I/O、存储映射 I/O 以及文件锁。

非阻塞 I/O

        关于“阻塞”一词前面已经给大家多次提到,阻塞其实就是进入了休眠状态,交出了 CPU 控制权。前面所学习过的函数,譬如 wait()、pause()、sleep()等函数都会进入阻塞,本文来聊一聊关于阻塞式 I/O 与 非阻塞式 I/O。

        阻塞式 I/O 顾名思义就是对文件的 I/O 操作(读写操作)是阻塞式的,非阻塞式 I/O 同理就是对文件的 I/O 操作是非阻塞的。这样说大家可能不太明白,这里举个例子,譬如对于某些文件类型(读管道文件、网络设备文件和字符设备文件),当对文件进行读操作时,如果数据未准备好、文件当前无数据可读,那么读操作可能会使调用者阻塞,直到有数据可读时才会被唤醒,这就是阻塞式 I/O 常见的一种表现;如果是非阻塞式 I/O,即使没有数据可读,也不会被阻塞、而是会立马返回错误!

        普通文件的读写操作是不会阻塞的,不管读写多少个字节数据,read()或 write()一定会在有限的时间内返回,所以普通文件一定是以非阻塞的方式进行 I/O 操作,这是普通文件本质上决定的;但是对于某些文件类型,譬如上面所介绍的管道文件、设备文件等,它们既可以使用阻塞式 I/O 操作,也可以使用非阻塞式 I/O 进行操作。

阻塞 I/O 与非阻塞 I/O 读文件

        本小节我们将分别演示使用阻塞式 I/O 和非阻塞式 I/O 对文件进行读操作,在调用 open()函数打开文件时,为参数 flags 指定 O_NONBLOCK 标志,open()调用成功后,后续的 I/O 操作将以非阻塞式方式进行; 这就是非阻塞 I/O 的打开方式,如果未指定 O_NONBLOCK 标志,则默认使用阻塞式 I/O 进行操作。

        对于普通文件来说,指定与未指定 O_NONBLOCK 标志对其是没有影响,普通文件的读写操作是不会阻塞的,它总是以非阻塞的方式进行 I/O 操作,这是普通文件本质上决定的,前面已经给大家进行了说明。

        本小节我们将以读取鼠标为例,使用两种 I/O 方式进行读取,来进行对比,鼠标是一种输入设备,其对应的设备文件在/dev/input/目录下,如下所示:

        通常情况下是 mouseX(X 表示序号 0、1、2),但也不一定,也有可能是 eventX,如何确定到底是哪个设备文件,可以通过对设备文件进行读取来判断,譬如使用 od 命令: 

sudo od -x /dev/input/event3

 Tips:需要添加 sudo,在 Ubuntu 系统下,普通用户是无法对设备文件进行读取或写入操作。

 当执行命令之后,移动鼠标或按下鼠标、松开鼠标都会在终端打印出相应的数据,如下所示:

         如果没有打印信息,那么这个设备文件就不是鼠标对应的设备文件,那么就换一个设备文件再次测试, 这样就会帮助你找到鼠标设备文件。笔者使用的 Ubuntu 系统,对应的鼠标设备文件是/dev/input/event3。接 下来我们编写一个测试程序,使用阻塞式 I/O 读取鼠标。

        示例代码演示了以阻塞方式读取鼠标,调用 open()函数打开鼠标设备文件"/dev/input/ event3",以只读方式打开,没有指定 O_NONBLOCK 标志,说明使用的是阻塞式 I/O;程序中只调用了一次 read()读取鼠标。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void){
    char buf[100];
    int fd, ret;
    
    /* 打开文件 */
    fd = open("/dev/input/event3", O_RDONLY);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    /* 读文件 */
    memset(buf, 0, sizeof(buf));
    ret = read(fd, buf, sizeof(buf));
    if (0 > ret) {
        perror("read error");
        close(fd);
        exit(-1);
    }
    printf("成功读取<%d>个字节数据\n", ret);

    /* 关闭文件 */
    close(fd);
    exit(0);
}

        编译上述示例代码进行测试:

        执行程序之后,发现程序没有立即结束,而是一直占用了终端,没有输出信息,原因在于调用 read()之后进入了阻塞状态,因为当前鼠标没有数据可读;如果此时我们移动鼠标、或者按下鼠标上的任何一个按键,阻塞会结束,read()会成功读取到数据并返回,如下所示:

 

        打印信息提示,此次 read 成功读取了 48 个字节,程序当中我们明明要求读取的是 100 个字节,为什么 这里只读取到了 48 个字节?这里暂时先不去理会这个问题。 接下来,我们将示例代码修改成非阻塞式 I/O,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void){
    char buf[100];
    int fd, ret;
    
    /* 打开文件 */
    fd = open("/dev/input/event3", O_RDONLY | O_NONBLOCK);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    /* 读文件 */
    memset(buf, 0, sizeof(buf));
    ret = read(fd, buf, sizeof(buf));
    if (0 > ret) {
        perror("read error");
        close(fd);
        exit(-1);
    }
    printf("成功读取<%d>个字节数据\n", ret);

    /* 关闭文件 */
    close(fd);
    exit(0);
}

        修改方法很简单,只需在调用 open()函数时指定 O_NONBLOCK 标志即可,对上述示例代码进行编译测试:

        执行程序之后,程序立马就结束了,并且调用 read()返回错误,提示信息为"Resource temporarily unavailable",意思就是说资源暂时不可用;原因在于调用 read()时,如果鼠标并没有移动或者被按下(没有 发生输入事件),没有数据可读,故而导致失败返回,这就是非阻塞 I/O。

        可以对代码进行修改,使用轮训方式不断地去读取,直到鼠标有数据可读,read()将会成功 返回:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void){
    char buf[100];
    int fd, ret;
    
    /* 打开文件 */
    fd = open("/dev/input/event3", O_RDONLY | O_NONBLOCK);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    /* 读文件 */
    memset(buf, 0, sizeof(buf));
    for(;;){
        ret = read(fd, buf, sizeof(buf));
        if (0 < ret) {
            printf("成功读取<%d>个字节数据\n", ret);
            /* 关闭文件 */
            close(fd);
            exit(0);
        }
    }
}

 阻塞 I/O 的优点与缺点

        当对文件进行读取操作时,如果文件当前无数据可读,那么阻塞式 I/O 会将调用者应用程序挂起、进入休眠阻塞状态,直到有数据可读时才会解除阻塞;而对于非阻塞 I/O,应用程序不会被挂起,而是会立即返回,它要么一直轮训等待,直到数据可读,要么直接放弃!

        所以阻塞式 I/O 的优点在于能够提升 CPU 的处理效率,当自身条件不满足时,进入阻塞状态,交出 CPU 资源,将 CPU 资源让给别人使用;而非阻塞式则是抓紧利用 CPU 资源,譬如不断地去轮训,这样就会导致该程序占用了非常高的 CPU 使用率!

        执行非阻塞示例代码对应的程序时,通过 top 命令可以发现该程序的占用了非常高的 CPU 使用率,如下所示:

        其 CPU 占用率达到了 100%!在一个系统当中,一个进程的 CPU 占用率这么高是一件非常危险的事情。而阻塞式方式的代码中,其 CPU 占用率几乎为 0,所以就本文所举例子来说,阻塞式 I/O 绝地要优于非阻塞式 I/O,那既然如此,我们为何还要介绍非阻塞式 I/O 呢?下一节我们将通过一个例子给大家介绍,阻塞式 I/O 的困境!

使用非阻塞 I/O 实现并发读取

        上一小节给大家所举的例子当中,只读取了鼠标的数据,如果要在程序当中同时读取鼠标和键盘,那又该如何呢?本小节我们将分别演示使用阻塞式 I/O 和非阻塞式 I/O 同时读取鼠标和键盘;同理键盘也是一种输入类设备,但是键盘是标准输入设备 stdin,进程会自动从父进程中继承标准输入、标准输出以及标准错误,标准输入设备对应的文件描述符为 0,所以在程序当中直接使用即可,不需要再调用 open 打开。

        首先我们使用阻塞式方式同时读取鼠标和键盘,示例代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define MOUSE "/dev/input/event3"

int main(void)
{
    char buf[100];
    int fd, ret;
    /* 打开鼠标设备文件 */
    fd = open(MOUSE, O_RDONLY);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    /* 读鼠标 */
    memset(buf, 0, sizeof(buf));
    ret = read(fd, buf, sizeof(buf));
    printf("鼠标: 成功读取<%d>个字节数据\n", ret);

    /* 读键盘 */
    memset(buf, 0, sizeof(buf));
    ret = read(0, buf, sizeof(buf));    
    printf("键盘: 成功读取<%d>个字节数据\n", ret);

    /* 关闭文件 */
    close(fd);
    exit(0);
}

        上述程序中先读了鼠标,再接着读键盘,所以由此可知,在实际测试当中,需要先动鼠标在按键盘(按下键盘上的按键、按完之后按下回车),这样才能既成功读取鼠标、又成功读取键盘,程序才能够顺利运行结束。因为 read 此时是阻塞式读取,先读取了鼠标,没有数据可读将会一直被阻塞,后面的读取键盘将得不到执行。

        这就是阻塞式 I/O 的一个困境,无法实现并发读取(同时读取),主要原因在于阻塞,那如何解决这个问题呢?当然大家可能会想到使用多线程,一个线程读取鼠标、另一个线程读取键盘,亦或者创建一个子进程,父进程读取鼠标、子进程读取键盘等方法,当然这些方法自然可以解决,但不是我们要学习的重点。

        既然阻塞 I/O 存在这样一个困境,那我们可以使用非阻塞式 I/O 解决它,将代码修改为非 阻塞式方式同时读取鼠标和键盘。使用 open()打开得到的文件描述符,调用 open()时指定 O_NONBLOCK 标志将其设置为非阻塞式 I/O;因为标准输入文件描述符(键盘)是从其父进程进程而来,并不是在我们的程序中调用 open()打开得到的,那如何将标准输入设置为非阻塞 I/O,可以使用fcntl()函数。通过如下代码将标准输入(键盘) 设置为非阻塞方式:

int flag;

flag = fcntl(0, F_GETFL); //先获取原来的 flag
flag |= O_NONBLOCK; //将 O_NONBLOCK 标志添加到 flag
fcntl(0, F_SETFL, flag); //重新设置 flag

        则代码修改为:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define MOUSE "/dev/input/event3"

int main(void)
{
    char buf[100];
    int fd, ret,flag;
    /* 打开鼠标设备文件 */
    fd = open(MOUSE, O_RDONLY | O_NONBLOCK);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    /* 将键盘设置为非阻塞方式 */
    flag = fcntl(0, F_GETFL); //先获取原来的 flag
    flag |= O_NONBLOCK; //将 O_NONBLOCK 标准添加到 flag
    fcntl(0, F_SETFL, flag); //重新设置 flag

    for(;;){
        /* 读鼠标 */
        ret = read(fd, buf, sizeof(buf));
        if(0 < ret)
            printf("鼠标: 成功读取<%d>个字节数据\n", ret);

        /* 读键盘 */
        ret = read(0, buf, sizeof(buf));   
        if(0 < ret) 
            printf("键盘: 成功读取<%d>个字节数据\n", ret);
    }
    /* 关闭文件 */
    close(fd);
    exit(0);
}

         将读取鼠标和读取键盘操作放入到一个循环中,通过轮训方式来实现并发读取鼠标和键盘,对上述代码 进行编译,测试结果:

        

        这样就解决了阻塞所出现的问题,不管是先动鼠标还是先按键盘都可以成功读取到相应数 据。 虽然使用非阻塞 I/O 方式解决了问题,但由于程序当中使用轮训方式,故而会使得该程序的 CPU 占用率特别高,终归还是不太安全,会对整个系统产生很大的副作用,如何解决这样的问题呢?我们将在下一篇文章向大家介绍。

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

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

相关文章

使用大语言模型集成工具 LangChain 创建自己的论文汇总和查询工具

Langchain可以帮助开发人员构建由大型语言模型(llm)支持的应用程序。它提供一个框架将LLM与其他数据源(如互联网或个人文件)连接起来。这允许开发人员将多个命令链接在一起&#xff0c;以创建更复杂的应用程序。包括最近比较火爆的AutoGPT等都是使用了Langchain框架进行开发的。…

【框架源码】Spring源码底层IOC容器加入对象的方式

1.Spring容器加入对象方式简介 使用XML配置文件 在XML配置文件中使用< bean >标签来定义Bean&#xff0c;通过ClassPathXmlApplicationContext等容器来加载并初始化Bean。 使用注解 使用Spring提供的注解&#xff0c;例如Component、Service、Controller、Repository等注…

一篇带你看懂什么是DesignOps(设计运营管理)

“好设计就是好生意”(Good design is good business) ----IBM创始人Thomas J. Watson早在20世纪50年代&#xff0c;IBM就开始关注好设计。IBM创始人Thomas J. Watson认为好的设计能很大程度提高产品的质量、功能和美观度&#xff0c;吸引更多的消费者和客户&#xff0c;增加销…

工程监测无线中继采集仪的寄存器(参数)汇总详解

工程监测无线中继采集仪的寄存器&#xff08;参数&#xff09;汇总详解 一、 寄存器&#xff08;参数&#xff09;汇总 无线中继采集发送仪有很多参数&#xff08;寄存器&#xff09;&#xff0c;对于一些简单的应用&#xff0c;用户无需关心这些参数&#xff0c;使用默认参数值…

【GPT-4理论篇-1】GPT-4核心技术探秘 | 京东云技术团队

前言 GPT-4已经发布有一段时间了&#xff0c;但是出于安全性等各种原因&#xff0c;OpenAI并没有公布GPT-4的技术细节和代码&#xff0c;而是仅仅给出了一个长达100页的技术报告[1]。 这个技术报告着重介绍了GPT-4的强大之处&#xff0c;仅仅给出了几个技术方向的概括&#x…

《汇编语言》- 读书笔记 - 实验

《汇编语言》- 读书笔记 - 实验 实验 1 查看 CPU 和内存&#xff0c;用机器指令和汇编指令编程1. 预备知识: Debug 的使用r 修改或显示寄存器的值d 查看内存数据e 编辑内存中指定地址的内容u 显示代码t 单步执行一条指令a 写入汇编指令g 执行到指定位置n 指定文件名L 加载文件W…

可视化大屏就是“面子工程”?那是你压根不了解大屏的真正功能

我经常收到这样的私信“企业投入可视化大屏有必要吗&#xff1f;有什么好处呢&#xff1f;”我想说在当前数据时代&#xff0c;企业提高自身竞争力多在这方面下功夫是有必要的。说“没用”的人&#xff0c;都是大屏做的失败的人群&#xff0c;其实“面子”作用确实有&#xff0…

HTTP的协议格式与Fiddler的应用

HTTP的协议格式与Fiddler的应用 &#x1f50e;HTTP协议是什么&#x1f50e;HTTP协议的工作过程&#x1f50e;HTTP的协议格式Fiddler 的应用抓包工具原理协议格式总结 &#x1f50e;结尾 &#x1f50e;HTTP协议是什么 HTTP(超文本传输协议)是一种应用非常广泛的应用层协议 对于…

栈和队列OJ题:LeetCode--20.有效的括号

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;今天给大家带来的是LeetCode--20.有效的括号 数 据 结 构 专 栏&#xff1a;数据结构 个 人 主 页 &#xff1a;stackY、 LeetCode 专 栏 &#xff1a;LeetCode刷题训练营 LeetCode--20.有效的括号&#xff1a;htt…

TypeError Object of type int64 is not JSON serializable

TypeError: Object of type int64 is not JSON serializable debug解析&#xff0c;在正常处理数据过程中&#xff0c;把模型预测结果&#xff0c;其他结构化数据转为str保存时&#xff0c;常常用到 json.dumps()&#xff0c;报错内容如标题所示。 原因&#xff1a;json.dump…

Vue图片处理解决方案,一篇就够了

文章目录 一、阅读须知二、解决的问题三、知识储备四、解决方案五、核心代码六、参考资料七、FileReader()对象八、Exif库使用九、Canvas画布十、动态创建标签并添加绑定事件十一、utils方法十二、vue中图片预览十三、工具网站 一、阅读须知 小帅爆肝整理的这篇文章&#xff0c…

华为OD机试真题 Java 实现【异常的打卡记录】【2023Q1 100分】

一、题目描述 考勤记录是分析和考核职工工作时间利用情况的原始依据&#xff0c;也是计算职工工资的原始依据。 为了正确地计算职工工资和监督工资基金使用情况&#xff0c;公司决定对员工的收集打卡记录进行异常排查。 如果出现以下两种情况&#xff0c;则认为打卡异常&…

持续引领 | 通付盾入选数说安全《2023年中国网络安全市场全景图》!

5月16日&#xff0c;数说安全正式发布《2023年中国网络安全市场全景图》&#xff08;以下简称全景图&#xff09;&#xff0c;这是自2018年开始&#xff0c;数说安全发布的第六版全景图。 其中&#xff0c;通付盾入选自动化攻击防护&#xff08;Anti-Bot&#xff09;、移动应用…

【软件工程】面向对象开发全面总结

文章目录 前言一、引言二、面向对象开发的含义三、面向对象开发的原则1. 封装原则2. 继承原则3. 多态原则4. 抽象原则5. 接口隔离原则6. 依赖倒置原则 四、面向对象开发的基本原则1. 单一职责原则2. 开放封闭原则3. 里氏替换原则 五、面向对象开发的方法1. 面向对象分析&#x…

阿里云服务器安装MySQL的具体步骤

1.首先打开阿里云&#xff0c;找到自己对于的公网ip 2.这里我使用MobaXterm的远程连接工具&#xff0c;这个工具还是很不错的耶&#xff0c;就是复制上图的公网ip地址 3.填写完成之后&#xff0c;会让你输入你阿里云服务端登入的密码&#xff0c;也就是你阿里云重置实例的密码&…

深度学习的十条调参经验

目录 法则一:调参、模型和数据 1、调参是锦上添花的事 2、模型 3、数据

富文本编辑器 kindeditor 使用整理

kindeditor富文本编辑器忘记什么时候添加到项目中了&#xff0c;最近做一个功能需要在原有编辑器上自定义添加一些固定内容&#xff0c;于是对着编辑器文档研究了一番&#xff0c;在这里做一个使用方式整理。 1.下载kindeditor 下载地址如下&#xff1a; https://github.com/…

视频平台。。。。 。

1.py 读取图片视频流 from flask import Flask from flask import render_template import os images_path_list[‘D:\imgs\’filename for filename in os.listdir(‘D:\imgs\’) if filename.endswith(‘.jpg’)] print(images_path_list) def return_img_stream(img_local_p…

城市内涝监测预警系统解决方案

一、方案背景 城市化进程的逐渐加快&#xff0c;城市内人口大量增多&#xff0c;交通运输压力增大&#xff0c;城市建设越来越密集&#xff0c;道路铺建面积越来越多&#xff0c;城市化的发展改变了城市区域的地表环境&#xff0c;致使城市雨水不能像农村或是古代雨水那样&…

JVM调优神器,运用 Arthas 释放 Java应用性能的全部潜力

Arthas、jstack、jstat 和 jmap 都是 Java 调优工具&#xff0c;但它们之间有以下区别&#xff1a; Arthas 是一款全新的 Java 诊断与调试工具&#xff0c;不仅可以执行传统的 JVM 监控命令&#xff0c;还提供了许多其他高级特性&#xff0c;如实时监控、动态修改代码、反编译…