Linux 【C编程】IO进阶— 阻塞IO、非阻塞IO、 多路复用IO、 异步IO

文章目录

  • 1.阻塞IO与非阻塞IO
    • 1.1为什么有阻塞式?
    • 1.2非阻塞
  • 2.阻塞式IO的困境
  • 3.并发IO的解决方案
    • 3.1非阻塞式IO
    • 3.2多路复用IO
      • 3.2.1什么是多路复用IO?
      • 3.2.1多路复用IO select原理
      • 3.2.1多路复用IO poll原理
    • 3.3异步IO

1.阻塞IO与非阻塞IO

1.1为什么有阻塞式?

1.常见的阻塞:wait pause sleep 等函数 ; read或write某些文件
2.阻塞式的好处:在某些情况下,阻塞式 I/O 可以更有效地利用系统资源。在一些高负载场景下,阻塞式 I/O 可以避免频繁的上下文切换,降低系统开销。 阻塞式 I/O 的编程模型通常比较直观和易于理解。代码顺序执行,不需要太多复杂的逻辑来处理异步操作和事件回调。

1.2非阻塞

1.为什么要实现非阻塞?
非阻塞 I/O 允许程序在进行 I/O 操作时不被阻塞,可以继续执行其他任务。这对于高并发的应用程序尤其重要,可以充分利用系统资源,提高系统的吞吐量和性能。 在一些场景下,程序需要快速响应并处理多个客户端请求。如果使用阻塞 I/O,一个慢速的 I/O 操作可能会导致整个程序被阻塞,无法及时响应其他请求,而非阻塞 I/O 可以避免这种情况。非阻塞 I/O 可以与异步 I/O 结合,使得程序可以发起一个 I/O 操作后继续执行其他任务,当 I/O 完成时,系统通知程序并处理完成的数据。这种模式可以提高系统的并发性和性能。
2.如何实现非阻塞IO访问: O_NONBLOCK和fcntl

2.阻塞式IO的困境

以在程序中读取键盘为例

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
int main(){char buf[199];memset(buf,0,sizeof(buf));//读取键盘// 键盘就是标准输入, stdinprintf("before read.\n");read (0,buf,2);  //从键盘读取两个字节 就是0号文件标识符 read 本身就是阻塞式的printf("读出来的内容是 :【%s】\n",buf);return 0;
}

此时运行程序后,发现已经堵塞住了 正在等待输入
在这里插入图片描述
在程序中读取鼠标
鼠标设备本质上也是字符设备 在/dev/input中,使用cat 读取 mouse 可能有多个 哪个晃动鼠标能得到数据就说明是鼠标设备,至于乱码是因为读取的是二进制数据,不是普通的字符。
在这里插入图片描述
代码演示:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){char buf[199];memset(buf,0,sizeof(buf));//读取鼠标// 鼠标不是标准输入 需要open 打开int fd = -1;fd = open("/dev/input/mouse0",O_RDONLY);if(fd<0){perror("open:");return -1;}printf("before read mouse.\n");read (fd,buf,2);  //从鼠标读取两位printf("mouse读出来的内容是 :【%s】\n",buf);return 0;
}

如果程序同时读取键盘和鼠标

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){char buf[199];memset(buf,0,sizeof(buf));//读取鼠标// 鼠标不是标准输入 需要open 打开int fd = -1;fd = open("/dev/input/mouse0",O_RDONLY);if(fd<0){perror("open:");return -1;}printf("before read mouse.\n");read (fd,buf,2);  //从鼠标读取两位printf("mouse读出来的内容是 :【%s】\n",buf);memset(buf,0,sizeof(buf));printf("before keyboard read.\n");read (0,buf,2);  //从键盘读取两个字节 就是0号文件标识符 read 本身就是阻塞式的printf("keyboard 读出来的内容是 :【%s】\n",buf);return 0;
}

如果用户使用这个程序,先使用鼠标,再使用键盘输入,程序没有问题
在这里插入图片描述
当用户先输入键盘的时候,阻塞IO就出现问题了,程序必须先等待鼠标事件,当前程序已经被阻塞住了,无论怎么输入键盘,程序也不会有响应。例如下图
在这里插入图片描述

3.并发IO的解决方案

3.1非阻塞式IO

使用fcntl 修改0号文件标识符的属性,添加非阻塞属性。
鼠标是通过open 打开的,直接添加非阻塞属性即可。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){char buf[199];memset(buf,0,sizeof(buf));int ret = -1;// 鼠标不是标准输入 需要open 打开int fd = -1;fd = open("/dev/input/mouse0",O_RDONLY|O_NONBLOCK);  //添加非阻塞属性if(fd<0){perror("open:");return -1;}int flag=  -1;//把0号描述符变成非阻塞式的flag = fcntl(0,F_GETFL); //获取原来的flagflag |= O_NONBLOCK ; //添加非阻塞属性fcntl(0,F_SETFL,flag); //更新flagwhile (1){ret  = read (fd,buf,2);  //从鼠标读取if(ret>0){printf("mouse读出来的内容是 :【%s】\n",buf);memset(buf,0,sizeof(buf));}ret  = read (0,buf,10);  //从键盘读取 if(ret>0){printf("keyboard 读出来的内容是 :【%s】\n",buf);memset(buf,0,sizeof(buf));}}return 0;
}

与阻塞相比,想读入键盘就读入键盘,想读入鼠标就读入鼠标,提高并发性
在这里插入图片描述

3.2多路复用IO

3.2.1什么是多路复用IO?

多路复用 I/O 是一种机制,允许一个进程能够同时监视和处理多个 I/O 源,例如文件描述符、sockets 或其他文件 I/O。这些多路复用的系统调用允许程序等待多个 I/O 事件中的任何一个就绪,从而避免了阻塞等待单个 I/O 完成的情况,提高了程序的效率和并发处理能力。对外部还是阻塞式的,内部非阻塞式自动轮询多路IO看看哪个有数据,就先用哪个。

3.2.1多路复用IO select原理

在Linux系统上常见的多路复用IO技术包括 select poll

1.文件描述符集合:
select 使用三个文件描述符集合来表示待检查的文件描述符。这三个集合分别是读文件描述符集合(readfds)、写文件描述符集合(writefds)和异常文件描述符集合(exceptfds)。
2.超时设置:
select 允许设置一个超时时间,表示最长等待时间。当超时时间达到时,select 将返回,不再等待事件的发生。
3.轮询:
select 通过轮询检查文件描述符的状态,判断是否有可读、可写或异常事件发生。它会遍历指定的文件描述符集合,检查每个文件描述符的状态。
4.阻塞:
当没有任何文件描述符就绪时,select 可以阻塞程序,等待文件描述符变得可读、可写或发生异常。在这种情况下,它会一直等待,直到有文件描述符就绪或超时发生。
5.就绪文件描述符集合:
select 在返回时会修改传入的文件描述符集合,标识出哪些文件描述符已经就绪,可以进行相应的IO操作。

在使用 select 时,需要注意其效率问题,特别是在大规模的文件描述符集合中。因为 select 是线性扫描所有文件描述符的,当文件描述符数量增加时,性能可能会下降。在一些操作系统上,存在文件描述符数量的限制。
使用select实现同时读取键盘和鼠标数据,并且设置了3s超时 如果一直没有等到IO到达,就直接退出程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>int main() {char buf[199];memset(buf, 0, sizeof(buf));// 打开鼠标设备int mouse_fd = open("/dev/input/mouse0", O_RDONLY);if (mouse_fd < 0) {perror("open mouse:");return -1;}struct timeval timeout;timeout.tv_sec = 3;   // 设置秒数timeout.tv_usec = 0;  // 设置微秒数printf("before select.\n");while (1) {fd_set read_fds;FD_ZERO(&read_fds);FD_SET(0, &read_fds);   // 标准输入(键盘)FD_SET(mouse_fd, &read_fds);  // 鼠标// 使用select监听多个文件描述符int result = select(mouse_fd + 1, &read_fds, NULL, NULL, &timeout);if (result > 0) {//判断出来是键盘IO到了if (FD_ISSET(0, &read_fds)) {// 从键盘读取read(0, buf, sizeof(buf));printf("keyboard 读出来的内容是:%s\n", buf);memset(buf, 0, sizeof(buf));}//判断出是鼠标到了if (FD_ISSET(mouse_fd, &read_fds)) {// 从鼠标读取read(mouse_fd, buf, 2);  // 从鼠标读取两位printf("mouse 读出来的内容是:%s\n", buf);memset(buf, 0, sizeof(buf));}}if(result == 0){printf("select 等待超时\n");return -1;}}// 关闭鼠标设备close(mouse_fd);return 0;
}

3.2.1多路复用IO poll原理

poll 是 Linux 中用于多路复用 I/O 操作的系统调用之一,它允许一个进程等待多个文件描述符上的事件发生。poll 的原理如下:
1.准备文件描述符集合和事件关注列表: 在调用 poll 之前,应用程序需要创建一个 struct pollfd 数组,该数组包含了要监听的文件描述符以及对每个文件描述符关注的事件。每个 struct pollfd 结构体包括以下字段:

  • fd
  • events
  • revents

2.调用 poll 函数: 应用程序调用 poll 函数,传递准备好的 struct pollfd 数组及数组的长度。

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:指向struct pollfd数组的指针
  • nfds: 数组中结构体的数量
  • timeout:设置超时时间,-1代表一直等待

代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>int main() {char buf[199];memset(buf, 0, sizeof(buf));// 打开鼠标设备文件int mouse_fd = open("/dev/input/mouse0", O_RDONLY);if (mouse_fd < 0) {perror("open mouse:");return -1;}// 使用poll监听标准输入和鼠标输入struct pollfd fds[2];fds[0].fd = STDIN_FILENO;  // 标准输入fds[0].events = POLLIN ;  //事件为可读fds[1].fd = mouse_fd;  // 鼠标输入fds[1].events = POLLIN ;//事件为可读printf("Waiting for input...\n");while (1) {//这里的timeout 设置-1 代表一直等待int result = poll(fds, 2, -1);  // 阻塞等待事件发生if (result > 0) {if (fds[0].revents & (POLLIN | POLLPRI)) {// 从标准输入读取数据read(STDIN_FILENO, buf, sizeof(buf));printf("Keyboard input: %s\n", buf);}if (fds[1].revents & (POLLIN | POLLPRI)) {// 从鼠标设备读取数据read(mouse_fd, buf, sizeof(buf));printf("Mouse input: %s\n", buf);}} else if (result < 0) {perror("poll:");break;}}// 关闭文件描述符close(mouse_fd);return 0;
}

与select效果一致
在这里插入图片描述

3.3异步IO

在Linux中,异步IO(Asynchronous I/O)是一种文件IO操作的模型,与传统的同步IO模型(例如使用read和write函数)不同。在异步IO模型中,IO操作的请求被提交后,程序可以继续执行其他任务,而无需等待IO操作完成。
异步IO的关键特点包括:

  • 非阻塞: 异步IO允许程序在等待IO操作完成的同时继续执行其他任务,不会阻塞整个进程。

  • 回调机制: 异步IO通常通过回调机制来处理IO操作完成的通知。当IO操作完成时,系统会调用预先注册的回调函数,以便程序可以处理IO的结果。

  • 提高并发性: 异步IO适用于需要同时处理大量IO操作的场景,能够提高程序的并发性能。
    在Linux中,异步IO可以通过以下几种机制来实现:

  • fcntl+signal:使用fcntl中的F_SETOWN和O_ASYNC选项来设置异步IO的所有者,然后结合signal来注册信号处理函数,以便在IO事件发生时得到通知。

  • aio_ 函数族:* 提供了一组异步IO相关的系统调用,例如aio_read、aio_write等。这些函数使用结构体struct aiocb来描述IO操作,并可以设置回调函数。

  • epoll: epoll本身是一个多路复用机制,但也可以与异步IO结合使用,通过epoll监听IO事件,当IO操作完成时,通过回调机制处理。

  • libuv: 是一个跨平台的异步IO库,它封装了不同操作系统的异步IO机制,使得在不同平台上能够使用相似的异步IO接口。

使用fcntl+signa完成异步IO操作

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
#include<signal.h>
int mouse_fd = -1;
//设置信号回调函数  鼠标时间设置为一个异步IO
void func(int sig){char buf[100] = {0};if (sig !=SIGIO)read (mouse_fd,buf,2);  //从鼠标读取两位printf("mouse读出来的内容是 :【%s】\n",buf);
}
int main(){char buf[100] = {0};//读取鼠标int flag = -1;mouse_fd = open("/dev/input/mouse0",O_RDONLY);if(mouse_fd<0){perror("open:");return -1;}//注册异步通知 把鼠标设置为异步IO事件flag  = fcntl(mouse_fd,F_GETFL);flag |= O_ASYNC;fcntl(mouse_fd,F_SETFL,flag);//把当前进程设置异步IO接收的进程fcntl(mouse_fd,F_SETOWN,getpid());// 注册信号处理函数signal(SIGIO, func);memset(buf,0,sizeof(buf));printf("before keyboard read.\n");read (0,buf,2);  //从键盘读取两个字节 就是0号文件标识符 read 本身就是阻塞式的printf("keyboard 读出来的内容是 :【%s】\n",buf);return 0;
}

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

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

相关文章

【教3妹学编程-算法题】统计出现过一次的公共字符串

3妹&#xff1a;哈哈哈哈哈哈&#xff0c;太搞笑了~ 呵呵呵呵呵呵 2哥&#xff1a;3妹干嘛呢&#xff0c; 笑的这么魔性&#xff01; 3妹&#xff1a;在看王牌对王牌&#xff0c;老搞笑了 2哥&#xff1a;这季好像没有贾玲吧。 3妹&#xff1a;是啊&#xff0c;听说贾玲去导电影…

Vue入门六(前端路由的概念与原理|Vue-router简单使用|登录跳转案例|scoped样式)

文章目录 前要&#xff1a;前端路由的概念与原理1&#xff09;什么是路由2&#xff09;SPA与前端路由3&#xff09;什么是前端路由4&#xff09;前端路由的工作方式 一、Vue-router简单使用1&#xff09;什么是vue-router2) vue-router 安装和配置的步骤① 安装 vue-router 包②…

React Native 桥接组件封装原生组件属性

自定义属性可以让组件具备更多的灵活性&#xff0c;所以有必要在JS 层通过自定义属性动态传值。 一、添加原生组件属性 因为 ViewManager 管理了整个组件的行为&#xff0c;所以要新增组件属性也需要在这里面&#xff08;如 InfoViewManager&#xff09;进行定义。 1、在Inf…

从DETR到Mask2Former(1):DETR-segmentation结构全解析

网上关于DETR做的detection的解析很多&#xff0c;但是DETR做Segmentation的几乎没有&#xff0c;本文结合DETR的论文与代码&#xff0c;对DETR做一个详细的拆解。理解DETR是理解Mask2Former的基础。 首先得把DETR-segmentation给run起来。Github上DETR的repository&#xff0…

【python入门】day26:统计字符串中出现指定字符的次数

案例 实际上if name“main”:就相当于是 Python 模拟的程序入口 。由于模块之间相互引用&#xff0c;不同模块可能都有这样的定义&#xff0c;而入口程序只能有一个&#xff0c;选中哪个入口程序取决于 ** ** name** **的值。 代码 #-*- coding:utf-8 -*- #开发时间&#xff…

SQL-分页查询and语句执行顺序

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现错误&am…

Pytest插件pytest-cov:优雅管理测试覆盖率

在软件开发中&#xff0c;测试覆盖率是评估测试质量的关键指标之一。为了更方便地统计和管理测试覆盖率&#xff0c;Pytest插件"pytest-cov"应运而生。本文将介绍"pytest-cov"的基本用法和优雅管理测试覆盖率的方法。 什么是pytest-cov? pytest-cov 是Pyt…

Docker数据卷与拦截与目录拦截

目录 高级容器挂载技术深度解析引言数据卷挂载原理解析应用场景使用介绍 目录挂载原理解析应用场景使用介绍 总结 高级容器挂载技术深度解析 引言 容器技术的快速发展使得容器挂载技术变得愈发重要。在容器化应用中&#xff0c;数据卷挂载和目录挂载是两种常见的挂载方式&…

【Python机器学习】SVM——调参

下面是支持向量机一个二维二分类数据集的训练结果&#xff1a; import mglearn import matplotlib.pyplot as plt from sklearn.svm import SVCplt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False X,ymglearn.tools.make_handcrafted_dataset()…

ModuleNotFoundError: No module named ‘simple_knn‘

【报错】使用 AutoDL 复现 GaussianEditor 时引用 3D Gaussian Splatting 调用simple_knn 时遇到 ModuleNotFoundError: No module named ‘simple_knn‘ 报错&#xff1a; 【原因】 一开始以为是版本问题&#xff0c;于是将所有可能的版本都尝试了 (from versions: 0.1, 0.2…

笔试面试题——继承和组合

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、什么是菱形继承&#xff1f;菱形继承的问题是什么&#xff1f;二、什么是菱形虚拟继承&am…

SqlAlchemy使用教程(一) 原理与环境搭建

一、SqlAlchemy 原理及环境搭建 SqlAlchemy是1个支持连接各种不同数据库的Python库&#xff0c;提供DBAPI与ORM&#xff08;object relation mapper&#xff09;两种方式使用数据库。 DBAPI方式&#xff0c;即使用SQL方式访问数据库 ORM, 对象关系模型&#xff0c;是用 Python…

1.1 计算机网络在信息时代的作用

1.1 计算机网络在信息时代的作用 网络&#xff08;Network&#xff09;由若干结点&#xff08;Node&#xff09;和连接这些结点的链路&#xff08;Link&#xff09;所组成。网络中的结点可以是计算机、集线器、交换机或者路由器等。 图1-1 多个网络还可以通过路由器互连起来&a…

记录一下误删除libc.so.6的经历

起因&#xff1a; 在配置环境时&#xff0c;出现’GLIBCXX_3.4.29 not found’的错误&#xff0c;在解决这个问题的过程中&#xff0c;需要删除sudo rm /usr/lib/x86_64-linux-gnu/libstdc.so.6软连接&#xff0c;但是一不小心sudo rm /lib/x86_64-linux-gpu/libc.so.6&#xf…

使用主题模型和古老的人类推理进行无监督文本分类

一、说明 我在日常工作中不断遇到的一项挑战是在无法访问黄金标准标签的情况下标记文本数据。这绝不是一项微不足道的任务&#xff0c;在本文中&#xff0c;我将向您展示一种相对准确地完成此任务的方法&#xff0c;同时保持管道的可解释性和易于调整。 一些读者可能已经开始考…

docker-compose一键搭建zookeeper集群

1、setup.sh setup.sh脚本用来创建本地文件夹&#xff0c;这些文件夹会被挂载到docker容器。 #!/bin/bashmkdir -p "$PWD\zoo1\data" mkdir -p "$PWD\zoo1\datalog"mkdir -p "$PWD\zoo2\data" mkdir -p "$PWD\zoo2\datalog"mkdir -p…

计算机缺失msvcp120.dll的最新解决方法,实测可以完美修复

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp120.dll丢失”。msvcp120.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;它是运行许多基于Windows操作系统的应用程序所必需的动态链接库文件之一。如果计算机…

Jetson_yolov8_解决模型导出.engine遇到的问题、使用gpu版本的torch和torchvision、INT8 FP16量化加快推理

1、前情提要 英伟达Jetson搭建Yolov8环境过程中遇到的各种报错解决&#xff08;涉及numpy、scipy、torchvision等&#xff09;以及直观体验使用Yolov8目标检测的过程&#xff08;CLI命令行操作、无需代码&#xff09;-CSDN博客和YOLOv8_测试yolov8n.pt&#xff0c;yolov8m.pt训…

[C#]winform部署yolov5-onnx模型

【官方框架地址】 https://github.com/ultralytics/yolov5 【算法介绍】 Yolov5&#xff0c;全称为You Only Look Once version 5&#xff0c;是计算机视觉领域目标检测算法的一个里程碑式模型。该模型由ultralytics团队开发&#xff0c;并因其简洁高效的特点而备受关注。Yol…

nova组件讲解和glance对接swift

1、openstack架构 &#xff08;1&#xff09;openstack是一种SOA架构&#xff08;微服务就是从这种架构中剥离出来的&#xff09; &#xff08;2&#xff09;这种SOA架构&#xff0c;就是把每个服务独立成一个组件&#xff0c;每个组件通过定义好的api接口进行互通 &#xff…