Linux--IO模型与高级IO重要概念

什么是IO?

IO是指计算机系统与外部世界进行数据交换的过程。在计算机中,IO通常用于与外部设备通信,这些设备包括键盘、鼠标、打印机、显示器、网络等。通过IO操作,计算机系统可以接收来自外部设备的输入数据,也可以将处理后的数据输出到外部设备。

在网络通信上,主要表示在计算机与计算机之间能通过互联网来进行数据交换,从而实现远程数据与资源的共享。

五种IO模型

阻塞IO

用户线程在发起IO请求后会阻塞,直到IO操作完成(内核数据准备好)并返回结果。这种方式简单但效率低下,特别是在高并发场景下。

非阻塞IO

如果内核还没有准备好数据,内核也会进行返回,并且返回一个EWOULDBLOCK错误码;

非阻塞IO需要在代码中循环,不断的查询读写这个文件描述符,这种操作称为轮询。在特定场景中,才会进行使用,会比较浪费CPU资源。

信号驱动IO

当内核将数据准备好时,内核会向应用进程发送一个SIGIO信号,通知程序进行IO操作。

IO多路转接

系统调用允许单个线程同时监视多个文件描述符的IO就绪状态,从而提高了IO操作的效率。

异步IO

异步IO是指系统内核主动发起IO请求,用户空间的线程被动接受IO操作的结果。这种方式下,用户线程无需等待IO操作完成即可继续执行其他任务,当IO操作完成后,内核会通知用户线程处理结果。

小结

任何IO过程,都包含两个步骤:等待数据 +拷贝数据

在实际应用中,等待消耗的时间往往大于拷贝消耗的时间。

所以,要想提高IO的效率,就要减少等待的消耗时间。

高级IO的重要概念

同步IO

同步IO是指需要等待前面程序完成后,才能继续执行后面的程序。这意味着当程序发起一个IO请求时,它会阻塞当前线程的执行,直到IO操作完成。

特点:

  • 阻塞性:同步IO在读写数据时会阻塞程序的执行,导致程序无法充分利用CPU资源。
  • 顺序性:同步IO按照程序指定的顺序依次执行IO操作,前一个IO操作完成后,下一个IO操作才会开始。
  • 简单性:同步IO的实现相对简单,因为程序只需按照顺序执行IO操作,无需处理复杂的并发逻辑。

同步IO在IO操作期间会阻塞程序执行,导致CPU资源无法得到充分利用。

当IO操作时间较长时,程序会处于等待状态,造成CPU资源的浪费。

同步IO适用于一些简单的、不需要高并发处理的场景。例如,批处理作业、简单计算和查询程序等。

在这些场景中,同步IO的阻塞性和顺序性并不会对程序的性能产生太大影响。

异步IO

异步IO是指在进行数据读写操作时,程序无需等待IO操作完成。程序会发起IO操作,并立即返回一个标识符或回调函数,用于指示IO操作的状态或结果。

特点:

  • 非阻塞性:异步IO允许程序在发起IO请求后继续执行其他任务,无需等待IO操作完成。
  • 并发性:异步IO可以同时处理多个IO请求,提高了程序的并发处理能力。
  • 复杂性:异步IO的实现相对复杂,因为程序需要处理IO完成的通知和管理异步操作的状态。这通常涉及到事件循环、回调函数等概念。

异步IO可以充分利用CPU资源,因为程序可以在IO操作期间执行其他任务。

异步IO通过并发处理多个IO请求,提高了系统的整体性能和资源利用率。

异步IO适用于需要处理大量并发IO请求的场景。例如,Web服务器、数据库访问、网络编程等。

在这些场景中,异步IO的非阻塞性和并发性能够显著提高程序的执行效率和并发处理能力。

非阻塞IO的实现

对于C语言/C++来说,默认都是阻塞IO,如果要改为非阻塞IO,就要通过特定函数来进行实现;

fcntl()

在Linux系统中用于操作文件描述符的库函数。

cpp 复制代码
#include <fcntl.h>  
#include <unistd.h>  
  
int fcntl(int fd, int cmd, ... /* arg */ );

参数:

  • fd: 文件描述符,是一个非负整数,表示要操作的文件。
  • cmd: 一个命令,用于指定要执行的操作。不同的命令需要不同数量的附加参数。
  • ... /* arg */: 根据 cmd 命令的不同,可能需要一个或多个附加参数。这些参数通常是一个指向数据的指针。

常用命令

  • F_DUPFD: 复制文件描述符。
  • F_GETFD: 获取文件描述符标志。
  • F_SETFD: 设置文件描述符标志。
  • F_GETFL: 获取文件状态标志(如只读、非阻塞等)。
  • F_SETFL: 设置文件状态标志。
  • F_GETLK: 获取文件锁的状态。
  • F_SETLK: 设置文件锁(非阻塞)。
  • F_SETLKW: 设置文件锁(阻塞)。
cpp 复制代码
#include<iostream>
#include<unistd.h>
#include<fcntl.h>

void SetNonBlock(int fd)
{
    int fl = ::fcntl(fd, F_GETFL);//获取fd当前文件描述符状态,默认为阻塞IO
    if(fl < 0) 
    {
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);//将其设置为非阻塞IO
}
cpp 复制代码
int main()
{
    char buffer[1024];
    SetNonBlock(0);//设置成非阻塞的
    while (true)
    {
        //读取标准输入流的数据
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "Echo# " << buffer << std::endl;
        }
        else
        {
            //问题:我怎么知道是底层IO条件不就绪,还是读取错误了呢???
            // 底层IO条件就绪和读取错误采用的是同样返回值操作的
            if(errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "底层数据没有就绪, 下次在试试吧! 做做其他事情!" << std::endl;
                sleep(1);
                continue;
            }
            std::cout << "读取错误... s : " << s  << " errno: " << errno << std::endl;
            sleep(1);
        }
    }
    return 0;
}

在阻塞IO中,read函数如果没有接收到输入的数据,那么就会一直阻塞,直到有数据的输入;

非阻塞IO中,如果没有输入数据,那么read函数也不会阻塞,会因为while循环进行轮询,不断的执行当前主函数,一旦read函数收到数据,那么就会打印对应数据。

相关推荐
李的阿洁1 小时前
OSPF的不规则区域
运维·服务器·网络
网安老伯1 小时前
【2024版】最新kali linux入门及常用简单工具介绍(非常详细)零基础入门到精通,收藏这一篇就够了_kalilinux
linux·运维·服务器·开发语言·web安全·网络安全·xss
CopyLower2 小时前
如何保证下订单和扣款操作只能执行一次:技术详解
开发语言·php
Bonne journée2 小时前
python调用父类同名成员
开发语言·python·php
是安迪吖2 小时前
基于VirtualBox和Ubuntu的虚拟环境搭建
运维·ubuntu·云计算
wdxylb2 小时前
在远程非桌面版Ubuntu中使用Qt5构建Hello World项目
linux·ubuntu
skywalk81632 小时前
install fcitx chinese input at FreeBSD14.1
运维·服务器·freebsd
小羊在奋斗2 小时前
【Linux】包管理器、vim详解及简单配置
linux·运维·vim
CS_素锦少年2 小时前
Linux_kernel字符设备驱动12
linux·运维·服务器