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.hqwc.cn/news/343562.html

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

相关文章

红黑树之概述

红黑树 R-B Tree&#xff0c;全称是 Red-Black Tree&#xff0c;又称为“红黑树”&#xff0c;它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色&#xff0c;可以是红(Red)或黑(Black)。 红黑树的特性 &#xff08;1&#xff09;每个节点或者是黑色&…

3D PDF查看器HOOPS Publish助力Smartscape拓展日本AEC市场!

​ 公司&#xff1a;Smartscape Co., Ltd. 行业&#xff1a;建筑、工程和施工(AEC) 软件&#xff1a;适用于AEC行业的3D PDF工具 软件开发工具包&#xff1a;Hoops Publish HOOPS_3D软件开发工具_HOOPS中文网慧都科技是HOOPS全套产品中国地区指定授权经销商&#xff0c;提供3D…

python 爬虫 生成markdown文档

本文介绍的案例为使用python爬取网页内容并生成markdown文档&#xff0c;首先需要确定你所需要爬取的框架结构&#xff0c;根据网页写出对应的爬取代码 1.分析总网页的结构 我选用的是redis.net.com/order/xxx.html (如:Redis Setnx 命令_只有在 key 不存在时设置 key 的值。…

jmeter--3.使用提取器进行接口关联

目录 1. 正则表达式提取器 1.1 提取单个数据 1.2 名词解释 1.3 提取多个数据 2. 边界值提取器 2.2 名词解释 3. JSON提取器 3.1 Json语法 3.2 名词解释 3.3 如果有多组数据&#xff0c;同正则方式引用数据 1. 正则表达式提取器 示例数据&#xff1a;{"access_to…

C#编程-实现线程声明周期

实现线程声明周期 当System.Threading.Thread类的对象被创建的时候,线程的生命周期开始。线程的生命周期在完成任务时结束。在线程的生命周期中有各种状态。这些状态是: 未启动状态可运行状态不可运行状态死亡状态下图显示了线程的各种状态和引起线程从一个状态变为另一个状…

Unity网络通讯学习

---部分截图来自 siki学院Unity网络通讯课程 Socket 网络上的两个程序通过一个双向的通信连接实现数据交换&#xff0c;这个连接的一端称为一个 Socket &#xff0c;Socket 包含了网络通信必须的五种信息 Socket 例子{ 协议&#xff1a; TCP 本地&#xff1a; IP &#xff…

代码随想录二刷 |二叉树 | 二叉搜索树的最小绝对差

代码随想录二刷 &#xff5c;二叉树 &#xff5c; 二叉搜索树的最小绝对差 题目描述解题思路 & 代码实现递归法迭代法 题目描述 530.二叉搜索树的最小绝对差 给你一棵所有节点为非负值的二叉搜索树&#xff0c;请你计算树中任意两节点的差的绝对值的最小值。 示例&#…

VUE element-ui实现表格动态展示、动态删减列、动态排序、动态搜索条件配置、表单组件化。

1、实现效果 1.1、文件目录 1.2、说明 1、本组件支持列表的表头自定义配置&#xff0c;checkbox实现 2、本组件支持列表列排序&#xff0c;vuedraggable是拖拽插件&#xff0c;上图中字段管理里的拖拽效果 &#xff0c;需要的话请自行npm install 3、本组件支持查询条件动态…

【面试突击】注册中心面试实战

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理…

软件测评中心▏性能测试之压力测试、负载测试的区别和联系简析

在如今的信息时代&#xff0c;软件已经成为人们日常工作和生活不可或缺的一部分。然而&#xff0c;随着软件的发展和应用范围的不断扩大&#xff0c;软件性能的优劣也成为了影响用户使用体验的重要因素。 软件性能测试即对软件在不同条件下的性能进行评估和验证的过程。通过模…

Wpf 使用 Prism 实战开发Day11

仓储&#xff08;Repository&#xff09;/工作单元&#xff08;Unit Of Work&#xff09;模式 仓储&#xff08;rep&#xff09;:仓储接口定义了对实体类访问数据库及操作的方法。它统一管理数据访问的逻辑&#xff0c;并与业务逻辑层进行解耦。 简单的理解就是对访问数据库的一…

Tensorflow2.0笔记 - 修改形状和维度

本次笔记主要使用reshape&#xff0c;transpose&#xff0c;expand_dim&#xff0c;和squeeze对tensor的形状和维度进行操作。 import tensorflow as tf import numpy as nptf.__version__#tensor的shape和维数获取 #假设下面这个tensor表示4张28*28*3的图片 tensor tf.rando…