🔥个人主页:Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

🌟心向往之行必能至

 一.五种IO模型

我们常说IO慢,且在前面信号也进行了测试,但为什么比较慢呢?

其实是系统调用,读取和写入慢

如write read 如果缓冲区满了或者空了,那么你就无法写或者读,此时就需要一直等待

read()->Input -> 等待 + 拷贝

write() -> Output -> 等待 + 拷贝

所以 其实 

IO = 等+ 拷贝 

其中等的占比时间远远大于拷贝的

那么高效IO就是单位时间内,减少IO中等待的比重

1.1 引入例子理解

在正式讲之前,我们先用个钓鱼的例子,初步认识

钓鱼 = 等 + 拉(将🐟从水里拷贝到桶里)


在一个多🐟的湖里,张三前往钓鱼

其中张三是专注钓鱼的,鱼漂不动,张三不动                               ---阻塞IO

李四也前往钓鱼,他看到张三就是只有鱼漂动了,张三才会动

李四则是钓鱼途中,一会玩下手机,一会看会书,一会看下鱼漂有没有动 --- 非阻塞IO

王五又前来了,不过与他们不同的是

王五在自己的钓竿上绑了一个铃铛,当鱼上钩,铃铛就会响,王五拷贝,否则王五则是专注干自己的事,不关注钓鱼                                                                                                --- 信号驱动IO

赵六是村里的有钱人,也前来钓鱼,不同的是,他带了100条鱼竿来钓

如果 1号鱼竿的鱼漂动力,赵六就去1号鱼竿拷贝收🐟  33号动了就去33号拷贝收🐟                                                                                                                              ---- 多路复用,多路转接

田七 是县里的有钱人,坐着司机小王开的车来的,且明确知道,自己是喜欢吃鱼,而非钓鱼,正当田七要去钓鱼时,公司打来了电话,请他去开会,因此田七就嘱托小王去钓鱼,不管他怎么钓,但钓到了就打电话通知他                                                                                           ----异步IO

1.2 问题

问题1:阻塞vs非阻塞

阻塞会因为IO条件不具备,阻塞会卡住,直到条件就绪

非阻塞:检测到IO条件不具备,出错返回

不同:等待方式不同  

我们常说,非阻塞IO效率高? 但这是错的

IO效率高本质是指单位时间内,IO中等待的比重小,而非阻塞只是做了更多的事情,并未降低比重

问题2:谁的钓鱼效率最高? 即IO效率

当然是赵六了,他有100条鱼竿,钓到的几率大,那么等待的占比就变小了

问题3:王五有没有等待?

王五也等待了,只是他不需要进行检测,但还是参与了钓鱼过程  拉🐟

如果王五不用等待,那么他为什么不直接走

结论 4

阻塞 非阻塞 信号驱动 多路复用   ---同步IO   ---->只要参与了IO(等.拷贝),就是同步

问题5:同步IO vs 异步IO

IO=等+拷贝         

凡是参与了等 拷贝的任意一步,就是同步  否则异步

1.3 正式认识

阻塞IO: 在内核将数据准备好之前, 系统调⽤会⼀直等待. 所有的套接字, 默认都是阻塞⽅式.
阻塞IO是最常⻅的IO模型.

⾮阻塞IO: 如果内核还未将数据准备好, 系统调⽤仍然会直接返回, 并且返回EWOULDBLOCK错误
码.
⾮阻塞IO往往需要程序员循环的⽅式反复尝试读写⽂件描述符, 这个过程称为轮询. 这对CPU来说是较⼤的浪费, ⼀般只有特定场景下才使⽤.

信号驱动IO: 内核将数据准备好的时候, 使⽤SIGIO信号通知应⽤程序进⾏IO操作.  触发硬件中断
IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核⼼在于IO多路转接能够同时等待多
个⽂件描述符的就绪状态.

异步IO: 由内核在数据拷⻉完成时, 通知应⽤程序(⽽信号驱动是告诉应⽤程序何时可以开始拷⻉数
据)

小结

任何IO过程中, 都包含两个步骤. 第⼀是等待, 第⼆是拷⻉. ⽽且在实际的应⽤场景中, 等待消耗的
时间往往都远远⾼于拷⻉的时间. 让IO更⾼效, 最核⼼的办法就是让等待的时间尽量少.

1.4 高级IO重要概念

1.4.1 同步通信 vs 异步通信(synchronous communication/ asynchronous communication)

同步和异步关注的是消息通信机制.
所谓同步,就是在发出⼀个调⽤时,在没有得到结果之前,该调⽤就不返回. 但是⼀旦调⽤返 回,就得到返回值了; 换句话说,就是由调⽤者主动等待这个调⽤的结果;
异步则是相反,调⽤在发出之后,这个调⽤就直接返回了,所以没有返回结果; 换句话说,当⼀ 个异步过程调⽤发出后,调⽤者不会⽴刻得到结果; ⽽是在调⽤发出后,被调⽤者通过状态、通 知来通知调⽤者,或通过回调函数处理这个调⽤.
另外, 我们回忆在讲多进程多线程的时候, 也提到同步和互斥. 这⾥的同步通信和进程之间的同步是完全 不相⼲的概念.
进程/线程同步也是进程/线程之间直接的制约关系
是为完成某种任务⽽建⽴的两个或多个线程,这个线程需要在某些位置上协调他们的⼯作次序⽽
等待、传递信息所产⽣的制约关系. 尤其是在访问临界资源的时候.

1.4.2 阻塞 vs ⾮阻塞

阻塞和⾮阻塞关注的是程序在等待调⽤结果(消息,返回值)时的状态.
阻塞调⽤是指调⽤结果返回之前,当前线程会被挂起. 调⽤线程只有在得到结果之后才会返回.
⾮阻塞调⽤指在不能⽴刻得到结果之前,该调⽤不会阻塞当前线程.
其他⾼级IO
⾮阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复⽤),readv和writev函数以及存储映 射IO(mmap),这些统称为⾼级IO.
我们此处重点讨论的是I/O多路转接

二. 非阻塞IO

2.1 fcntl

TL(2)                                                                   Linux Programmer's Manual                                                                   FCNTL(2)

NAME
       fcntl - manipulate file descriptor

SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>

       int fcntl(int fd, int cmd, ... /* arg */ );
传⼊的cmd的值不同, 后⾯追加的参数也不相同.
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).
我们此处只是⽤第三种功能, 获取/设置⽂件状态标记, 就可以将⼀个⽂件描述符设置为⾮阻塞.

代码实战,设计一个轮询的非阻塞IO

#include <iostream>
#include <unistd.h>
#include<fcntl.h>
#include<cstdio>
void SetNonBlock(int fd)
{
    int fl=fcntl(fd,F_GETFL);//先获得fl,再进行设置
    if(fl<0)
    {
        perror("fcntl");
        return;
    }
    fcntl(fd,F_SETFL,fl| O_NONBLOCK);//说明是位图
}
int main()
{
    SetNonBlock(0);
    char buffer[1024];
    while (true)
    {
        //Linux中,ctrl+d 标识输入结束,read返回为0 ,类似读取到文件结尾
        ssize_t n = read(0, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n - 1] = 0; // 防止输出换行符
            std::cout << buffer << std::endl;
        }
        else if(n<0)//非阻塞read    如果底层数据还没有准备好,算错误吗?不算,因为非阻塞IO应该有这样的打算
        {
            //1.读取错误 2. 数据还没准备好
            if(errno==EAGAIN||errno==EWOULDBLOCK)
            {
                //数据还没准备好
                std::cout<<"数据还没准备好"<<std::endl;
                sleep(1);
                //做自己的事情
                continue;
            }
            else if(errno==EINTR)
            {
                continue;//系统调用被打断  :一个系统调用(System Call)在完成之前被一个信号(Signal)中断了。
            }
        }
        else {
            break;
        }
        sleep(1);
        std::cout<<"."<<std::endl;//换行符我为了刷新c的缓冲区
    }

    return 0;
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐