【linux高级IO(一)】理解五种IO模型
CSDN 2024-07-07 09:37:13 阅读 62
💓博主CSDN主页:杭电码农-NEO💓
⏩专栏分类:Linux从入门到精通⏪
🚚代码仓库:NEO的学习日记🚚
🌹关注我🫵带你学更多操作系统知识
🔝🔝
Linux高级IO
1. 前言2. 重谈对IO的理解3. 阻塞IO讲解4. 非阻塞IO讲解5. 信号驱动IO6. IO多路转接7. 异步IO8. 理解异步和同步9. 总结以及拓展
1. 前言
本篇文章开始, 将与大家分享高级IO相关的内容
本章重点:
本篇文章会带大家初识五种常见的IO模型, 分别是: 阻塞IO, 非阻塞IO, 信号驱动IO, IO多路转接, 异步IO. 其中, 多路转接将会是本系列文章后续的重点
2. 重谈对IO的理解
IO: input or output --> 访问外设 效率低
IO一定是非常低效的, 以读取为例:
当我们read/recv时, 如果底层缓冲区<code>没有数据, read/recv函数会阻塞
当我们read/recv时, 如果底层缓冲区
有数据
, read/recv函数会拷贝
所以说, IO = 等待 + 拷贝 !!!
记住这句话, 会一直贯穿整个IO系列文章
探讨低效IO和高效IO:
很明显, IO = 等待 + 拷贝.
所以可以得出下面的结论:
低效IO: 单位时间内, 大部分时间, IO类接口都在等待高效IO: 让等待的比重降低
接下来的五种IO模型, 就是围绕着是否高效进行的
3. 阻塞IO讲解
正如其名, 阻塞IO 在内核将数据准备好之前, 系统调用会一直等待, 并且所有的套接字默认都是阻塞IO. 阻塞IO是最常见的IO模型, 它比较好理解, 下面是关于阻塞IO的图像讲解
通俗来讲, 阻塞IO就是, 你去河边钓鱼, 只拿一根鱼竿等于上钩, 并且时刻盯着水面
4. 非阻塞IO讲解
顾名思义, 非阻塞IO就是说, 当底层数据没有准备就绪时, 不会傻傻的等待, 而是直接返回. 但是调用recv时, 出现错误也会直接返回, 应该怎样区分这两种情况呢? 答案是阻塞式IO的正常返回时, 会将errno全局遍历设置为宏: EWOULDBLOCK. 这下就能将它们区分开了
非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用. 下面是非阻塞IO的简单示例:
<code>#include <fcntl.h>
#include <unistd.h>
#include<errno.h>
#include <cstdlib>
#include <iostream>
using namespace std;
//对指定的fd设置非阻塞
void SetNonBlock(int fd) {
int fl = fcntl(fd, F_GETFL);
if (fl < 0) {
cerr << "fcntl error" << endl;
exit(1);
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
int main() {
SetNonBlock(0);
while (1) {
char buffer[1024];
ssize_t s = read(0, buffer, sizeof(buffer) - 1);
if (s > 0) {
buffer[s] = 0;
cout << buffer << endl;
} else if (s == 0) {
cout << "读到文件结尾了" << endl;
break;
}
else
{
//1. 数据没用准备好 2. 真的出错了. 都以-1的返回值返回
// 数据没有准备好,不算出错. 需要区分这两种情况
if(errno == EWOULDBLOCK || errno == EAGAIN)
{
cout<<"os底层数据还没就绪"<<endl;
cout<<errno<<endl;
}
//被信号中断, 也不算read出错
else if(errno == EINTR)
{
cout<<"IO interrupted by signal"<<endl;
}
else
{
cout<<"read error"<<endl;
break;
}
}
sleep(1);
}
}
调用fcntl函数将fd设置为非阻塞
通俗来讲, 非阻塞IO就是, 你去河边钓鱼, 也只用一根鱼竿, 但是你过一分钟才去看看有没有鱼上钩, 其他时间你可能在刷抖音
5. 信号驱动IO
信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作. 也就是说信号驱动的方式你不用像非阻塞IO一样, 每过一段时间去检查是否有数据就绪, 一旦有数据就绪, 会有信号通知你, 这也就可以更多时间刷抖音了(不是)
通俗来讲, 信号驱动IO就是, 你去河边钓鱼, 也只拿一个鱼竿, 只不过鱼竿上有压力传感器, 一旦有鱼上钩就会发出声音提醒你. 其余时间我们当然可以愉快的刷抖音
6. IO多路转接
前面几个钓鱼的人是不是有点寒酸了?一次只拿一个鱼竿, 效率太低了吧! 多路转接直接把桌子掀了, 它拿了100个鱼竿去钓鱼: IO多路转接能够同时等待多个文件描述符的就绪状态
通俗来说就是你拿一百个鱼竿去钓鱼, 同时等待一百种可能, 一旦有鱼上钩了, 会同时把所有上钩的鱼都拉上来, 这效率简直是指数级增长, 所以这也是在实际生活中使用的最多的IO方案
7. 异步IO
前面所有的IO方式, 都是同步IO, IO=等待+拷贝, 同步IO就是要么参与了等待过程, 要么参与了拷贝过程, 要么都参与了. 而异步IO则是等待和拷贝都不参与: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).
还是拿钓鱼的例子来说, 前面的钓鱼者, 不管你一次性带多少鱼竿(多路转接), 不管你在鱼竿上安装什么高科技(信号驱动), 但是你总得去河边, 自己拿着鱼竿钓鱼. 而异步IO是怎么做的呢? 他直接雇佣了一个人帮它去钓鱼, 什么时候鱼上钩, 你等待了多久我都不在乎, 我只需要你在晚上九点的时候将钓的鱼全部带给我即可.
8. 理解异步和同步
同步和异步关注的是消息通信机制.
同步和异步在实际场景中怎样运用?
虽然说大部分IO类型都是同步IO, 但是实际生活中运用异步IO的概率也不小. 举个例子, 你是王者荣耀的后端, 一个英雄放了一个技能打在对面身上, 此时我们后端要将这个操作做成同步的还是异步的? 很明显是同步, 因为我想要实时的看见对面英雄的血条在减少. 再举个例子, 现在你是QQ的后端, 你现在要查询一千万个QQ号中, 有哪些QQ号超过1个月没有上线了. 你把此功能做成同步还是异步? 很明显考虑到成本问题一定是做成异步, 一千万个QQ号如果用一台机器可能会查询几个小时, 你可能会说, 那我可以用多台机器做负载均衡, 是的没错, 但是机器数量多了, 成本就上去了. 所以做成异步的IO比较好
<code>综上所述, 实际场景中要根据自己的情况和需求来觉得使用同步还是异步, 不要觉得在学习时都用同步, 以后工作了也就无脑的用同步
9. 总结以及拓展
本篇文章只是简单的介绍了IO模型的几个分类, 其中, 最重要的模型是多路转接, 后面的文章会着重讲解它. 多路转接为什么重要? 因为它是业内最常用的用来提高并发性的模型, 后续大家都接触都reactor模型, 而reactor模型可以有多种实现方式, 而效率最高的reactor模型则是用多路转接实现的!!!
🔎
下期预告:多路转接之select 🔍
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。