五种IO模型

我们在进行IO的时候,分为两个过程等待资源就绪+拷贝。
因此IO=等+拷贝。
那么所谓的高效IO,就是降低等的占比

五种IO模型

我先以生活中钓鱼的例子来形容五种IO模型。

来钓鱼的人会有各种不同的行为。

首先是我们的张三,这是个非常专注的钓鱼者,他会全神贯注盯着鱼竿,期间不做任何事。

随之而来的是李四,李四则是在钓鱼的时候刷刷视频,时不时看看鱼竿有没有动静。

王五钓鱼又不同于他们,他将一个铃铛系在鱼竿上,然后就专心干自己的事情不去理会鱼竿,只有铃铛响了他才会去收杆。

赵六则是方圆十里的首富,他带来一百根鱼竿一起钓鱼,不断来回跑看看哪根鱼竿上钓了。

最后是田七,田七则不享受钓鱼的过程,只想要鱼,因此他不参与钓鱼的过程,让自己的手下去钓鱼。

我们将上述钓鱼过程中的人看成系统调用,鱼竿就是socket,湖是系统内部,🐟相当于数据,鱼漂浮动就是数据就绪,钓就是发生拷贝。

那么上述五种钓鱼行为对应五种钓鱼模型。

张三:阻塞IO

李四:非阻塞IO

王五:信号驱动IO

赵六:多路复用/多路转接IO

田七:异步IO

事实上,阻塞IO才是最常见的IO,占90%以上。因为他十分简单。所有的套接字,默认都是阻塞方式。

上述IO效率最高的自然就是多路复用IO。

同步通信vs异步通信

同步通信synchronouscommunication

异步通信asynchronous communication

我们将上述五种IO模型中的前四种称为同步IO,最后一种称为异步IO。

这时候就有人提出异议了:"诶,前面信号部分不是说过信号是异步的吗,那为什么信号驱动IO不是异步的?"

这里就要明确一下,这两个异步和同步不是一个概念。

通信中的同步和异步关注的是消息通信机制:

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就得到返回值了;换句话说,就是由调用者主动等待这个调用的结果;
  • 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

而我们进程、线程中的同步讲的是两种执行流的执行呈现一定顺序性。

非阻塞IO

我们这里浅浅讲一下非阻塞IO怎么实现。

我们正常打开open打开文件是阻塞方法打开的。注意到我们的open其实是可以传三个参数的:

我们传入O_NONBLOCK 或 O_NDELAY就能以非阻塞方式打开文件。

但是这样有违我们正常的编写习惯,这里还有个能将阻塞方法打开的文件描述符改为非阻塞的替代方案:

fcntl 函数有5种功能:

• 复制一个现有的描述符(cmd=F_DUPFD).

• 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

• 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

• 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

• 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

我们此处只是用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞.

如此我们就能实现一个将文件描述符设置为非阻塞的函数:

然后我们再编写一下主函数非阻塞轮询逻辑,还记得这个在我们进程等待的时候也用过非阻塞轮询的方法。

再次之前我们要知道read如果在非阻塞轮询时,没有读到数据会返回什么:

是的,读到数据就会出错返回。

那怎么区分真的出错还是没读到数据呢?

我们可以根据他设置的错误码不同,如果是没读到数据设置的错误码是:

EAGAIN 或EWOULDBLOCK。其实这两是一个值:

除此之外,read还有可能因为信号中断,这时错误码会设置为:

那么对这些特殊情况稍微处理一下之后:

cpp 复制代码
int main()
{
    char buffer[1024];
    SetNonBlock(0);
    while(true)
    {
        printf("Enter# ");
        fflush(stdout);
        ssize_t n = ::read(0, buffer, sizeof(buffer)-1);
        if(n > 0)
        {
            buffer[n] = 0;
            printf("echo# %s", buffer);
        }
        else if(n == 0)  // ctrl + d
        {
            printf("read done\n");
            break;
        }
        else
        {
            if(errno == EWOULDBLOCK)
            {
                sleep(1);
                std::cout << "底层数据没有就绪,开始轮询检测" << std::endl;
                std::cout << "可以做其他事情" << std::endl;
                continue;
            }
            else if(errno == EINTR)
            {
                continue;
            }
            else
            {
                perror("read");
                break;
            }
        }
    }

    return 0;
}

尝试运行:

非常完美!

但是非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询.这对CPU来说是较大的浪费,一般只有特定场景下才使用.

相关推荐
济6171 天前
linux 系统移植(第七期)----U-Boot 图形化配置及其原理-- Ubuntu20.04
linux·运维·服务器
kida_yuan1 天前
【Linux】文件系统与 fsck.ext4 修复 - 我踩过的坑与总结
linux·运维·网络
tobias.b1 天前
408真题解析-2009-33-网络-OSI模型
网络·计算机考研·408真题·408真题解析
米高梅狮子1 天前
01. 配置DHCP服务器
服务器·网络·php
馨谙1 天前
Linux面试题----文件权限,chmod,chown,suid,sgid,粘滞位,umask
linux·运维·服务器
这儿有一堆花1 天前
CDN 工作原理:空间换取时间的网络架构
网络·架构·php
徐子元竟然被占了!!1 天前
常用端口学习
运维·网络·学习
青衫客361 天前
Linux 磁盘挂载全流程实战——从新盘初始化到安全开机自启
linux·运维·服务器
轻造科技1 天前
设备点检系统+移动端APP:替代纸质点检表,漏检率降为0
网络·安全·web安全
Godspeed Zhao1 天前
现代智能汽车中的无线技术26——Wi-Fi(14)
网络·汽车·智能路由器