面试常问的Linux之 I/O 复用

I/O 复用

一、I/O的概念

在这里插入图片描述

在Linux系统中,I/O(输入/输出)指的是计算机系统的数据交换过程,包括从外部设备读取数据(输入)和将数据发送到外部设备(输出)。I/O操作是Linux系统中非常重要的一个方面,因为大多数应用程序都需要读取和写入数据。

在Linux中,I/O可以分为两类:阻塞I/O和非阻塞I/O。阻塞I/O指的是应用程序在进行I/O操作时会一直等待直到I/O操作完成后才能继续执行,而非阻塞I/O则允许应用程序在进行I/O操作的同时继续执行其他操作。

此外,Linux还提供了异步I/O和多路复用I/O。异步I/O指的是应用程序发起一个I/O操作后可以立即返回并继续执行其他操作,当I/O操作完成后再通过回调函数通知应用程序;而多路复用I/O则允许应用程序同时监视多个I/O操作的状态,从而在I/O操作可用时立即处理它们。

在Linux系统中,I/O操作通过系统调用来实现。常见的I/O系统调用包括read、write、open、close、select、poll、epoll等。这些系统调用允许应用程序与系统内核进行交互,从而实现I/O操作。

二、I/O 模型

在这里插入图片描述

2.1 背景知识

2.1.1 操作系统如何知道网卡中有数据

不同设备通过网络进行I/O 时,传输的数据会经过网卡,数据到来时,是如何通知操作系统的?

在Linux中,操作系统通过网络接口卡驱动程序(network interface card driver)来与网卡进行通信,并实现数据的传输和接收。当网卡中有数据到达时,网卡驱动程序会通过中断(interrupt)机制向操作系统内核发送一个中断信号,通知操作系统有数据到达

中断是一种CPU硬件机制,用于处理设备或程序发出的信号或事件。当网卡中有数据到达时,网卡会向CPU发送一个中断请求信号(Interrupt Request,IRQ),通知CPU需要处理该事件。操作系统内核会响应该中断请求,保存当前处理的上下文,并跳转到中断处理程序(Interrupt Service Routine,ISR)中处理该事件。

中断处理程序是一段特殊的程序代码,用于响应中断事件并处理相应的数据。在网络接口卡驱动程序中,中断处理程序会读取网卡中的数据,并将数据存储到操作系统内核的网络缓冲区中。然后,操作系统内核可以将数据传递给应用程序进行处理,或者将数据转发给其他设备进行处理。

需要注意的是,在高负载的情况下,中断处理程序可能会导致系统的性能瓶颈,因此Linux操作系统提供了一些优化机制,如基于轮询的中断处理程序(Polling Interrupt Service Routine,PISR)和中断合并(Interrupt Coalescing),以提高系统的性能和响应速度。

简单来说,是通过中断程序。外设中有数据时发送中断程序给CPU,CPU再把其数据拷贝到内存。

2.1.2 什么是高效的IO

在这里插入图片描述

高效的I/O是指操作系统在进行输入输出(I/O)操作时,能够以最快的速度完成数据传输,同时减少CPU等其他资源的使用,提高系统的性能和效率。

实现高效的I/O需要考虑以下几个方面:

  1. 避免阻塞I/O:阻塞I/O会使得应用程序在等待I/O操作完成时一直处于等待状态,从而浪费CPU资源,降低系统性能。采用非阻塞I/O或异步I/O可以避免这种情况。
  2. 减少上下文切换:I/O操作需要在用户空间和内核空间之间切换,这种切换需要消耗CPU资源。减少上下文切换可以提高I/O操作的效率。
  3. 优化I/O缓冲机制:操作系统通常会使用缓冲区来存储I/O数据,优化缓冲机制可以提高数据传输的效率。例如,采用预读取、预写入等技术可以避免重复的I/O操作。
  4. 选择合适的I/O接口和设备:不同的I/O接口和设备对数据传输的效率有影响。选择高效的I/O接口和设备可以提高数据传输的速度和效率。
  5. 使用多线程和多进程:多线程和多进程可以实现并发的I/O操作,提高系统的处理能力和响应速度。

对于I/O复用,主要是通过减少等待时间,而提高效率。

2.2 五种I/O 模型

在这里插入图片描述

我们可以通过一个钓鱼的例子来解释不同类型的I/O模型。

假设你在岸边钓鱼,以下是不同I/O模型的解释:

  1. 阻塞式I/O:你使用阻塞式I/O模型时,当你抛出鱼竿后,你必须一直等待鱼儿上钩。你不能做其他事情,直到你感觉到有重量在你的鱼竿上。在这种情况下,你被阻塞,只有等待直到有新的数据可供处理。
  2. 非阻塞式I/O:如果你使用非阻塞式I/O模型时,当你抛出鱼竿后,你可以做其他事情,例如看书或聊天。你只需要每隔一段时间检查你的鱼竿,看看是否有鱼上钩。这种方式允许你在等待I/O完成时同时进行其他操作。
  3. 信号驱动型I/O:在信号驱动型I/O模型中,你会设置一个信号处理程序(比如在鱼竿上系一个铃铛)来通知你何时有鱼上钩。当你抛出鱼竿后,你可以离开岸边,但是你必须将信号处理程序注册到操作系统中,以便在有鱼上钩时自动通知你。
  4. 异步I/O:在异步I/O模型中,你将在抛出鱼竿后告诉操作系统(帮你钓鱼的人)通知你何时有鱼上钩,然后你可以离开岸边。当有鱼上钩时,操作系统将通知你,并让你知道鱼的大小。这种方式允许你在等待I/O完成时同时进行其他操作。
  5. 多路转接:多路转接是一种特殊的I/O模型,它允许你在多个I/O操作之间切换。这意味着你可以同时监视多个鱼竿,而不必等待一个鱼竿上的鱼上钩。你可以抛出多个鱼竿,并在有鱼上钩时自动通知你。这种方式可以提高I/O操作的效率和吞吐量。

下面通过《UNIX网络编程卷1》的内容介绍五种模型。

2.2.1 阻塞式I/O

此例中基于UDP,将函数recvfrom视为系统调用。

进程调用recvfrom,从用户区切换到内核区,发现无数据报准备好,开始等待,直至准备好,然后将数据从内核拷贝至用户空间,完成拷贝后返回成功指示,最后处理数据报。

此系统调用直到数据报到达且拷贝到缓冲区或是出错才返回,出错一般是被信号中断。

2.2.2 非阻塞式I/O

当我们把一个套接口设置为非阻塞方式时,即通知内核:当请求的I/O 操作非得让进程睡眠不能完成时,不要让进程睡眠,而应返回一个错误。

进程调用recvfrom,内核此时无数据报准备好,返回一个EWOULDBLOCK错误,进程再次调用recvfrom,如果内核依旧无数据,会一直重复上述过程。如果内核数据报已准备好,则拷贝数据报到缓冲区,拷贝完成后,recvfrom返回成功指示,最后进程处理数据报。

当一个进程对一个非阻塞描述字不断重复调用recvfrom时,此过程可称为“轮询(polling)”。这种过程对CPU时间是极大的浪费

2.2.3 I/O 复用模型

有了I/O 复用,我们可以调用select 或poll,在这两个系统调用中的某一个上阻塞,而不是阻塞于真正的I/O 系统调用。

进程调用select系统调用,如果内核中无数据报准备,就受阻于select调用,等待可能多个套接口中的任一个变为可读。如果数据包准备好,select返回可读条件。接下来进程调用recvfrom,从内核拷贝数据报到用户空间,完成后返回成功指示,最后进程处理数据报。

我们阻塞于select调用,等待数据报套接口可读。当select返回套接口可读条件时,我们调用recvfrom将数据报拷贝到应用缓冲区中。

与阻塞I/O 相比,似乎没有显示什么优越性,还多了一个系统调用。实际上,相比阻塞式I/O,I/O复用的优势在于可以同时监视多个文件描述符的状态,从而避免了多个阻塞式I/O的等待时间叠加。

2.2.4 信号驱动 I/O 模型

我们也可以用信号,让内核在描述字准备好时用信号SIGIO通知我们,此方法即为信号驱动 I/O

首先,我们允许套接口进行信号驱动I/O,并通过系统调用 sigaction 安装一个信号处理程序。此系统调用立即返回,进程继续工作,它是非阻塞的。当数据报准备好被读时,就为该进程生成一个SIGIO信号。我们随即可以在信号处理程序中调用 recvfrom 来读数据报,最后处理数据报。

2.2.5 异步 I/O 模型

我们让内核启动操作,并在整个操作完成后(包括将数据从内核拷贝到我们自己的缓冲区)通知我们。这种模型和信号驱动模型相比:信号驱动是由内核通知我们何时启动一个 I/O 操作,而异步 I/O 模型是由内核通知我们 I/O 操作何时完成

我们调用函数aio_read函数,给内核传递描述字、缓冲区指针、缓冲区大小(与read相同的三个参数)、文件偏移(与lseek类似),并告诉内核当整个操作完成时如何通知我们。此系统调用立即返回,我们的进程不阻塞于等待I/O 操作的完成。我们要求内核在操作完成时生成一个信号,此信号直到数据已拷贝到应用缓冲区才产生,然后递交在aio_read中指定的信号,程序处理信号,再处理数据报。

三、 接口介绍

3.1 fcntl 设置非阻塞I/O

在 Linux 中,可以使用 fcntl 函数来设置文件描述符为非阻塞IO。fcntl 函数可以用于对已打开的文件描述符进行各种操作,包括设置文件描述符的属性、锁定文件以及获取文件状态等。

下面是一个示例代码,演示如何将文件描述符 fd 设置为非阻塞IO:

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <iostream>

using namespace std;

int setNonBlocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        cerr << "获取文件描述符属性失败:" << strerror(errno) << endl;
        return -1;
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        cerr << "设置非阻塞IO失败:" << strerror(errno) << endl;
        return -1;
    }
    return 0;
}

int main() {
    int fd = 0; // 假设需要设置为非阻塞IO的文件描述符为0,即标准输入
    if (setNonBlocking(fd) == -1) {
        cerr << "设置非阻塞IO失败" << endl;
        return -1;
    }
    cout << "设置非阻塞IO成功" << endl;
    return 0;
}

在上述代码中,我们定义了一个 setNonBlocking 函数来设置文件描述符为非阻塞IO。该函数首先获取文件描述符的属性 flags,然后通过 OR 操作将 O_NONBLOCK 标志添加到 flags 中,最后使用 fcntl 函数将新的属性值设置为文件描述符的属性。

需要注意的是,如果文件描述符本身就是非阻塞IO的,则添加 O_NONBLOCK 标志并不会产生任何效果。在使用非阻塞IO的过程中,需要注意处理 EAGAIN 和 EWOULDBLOCK 错误,这些错误表示当前操作需要等待更多数据或等待操作完成。

如果不进行上述处理,对该非阻塞的文件描述符进行read,我们的read是非阻塞的,一旦检测到底层没有数据就绪,我们的IO接口,会出错返回-1,但这并不是真正的错误,而是因为没有接收到数据。
可以通过errno来判断错误原因:数据没有就绪时,errno的值是EAGAIN或者EWOULDBLOCK,要处理可以用 if(errno == EAGAIN || errno == EWOULDBLOCK) 判断。

在 Linux 系统中,errno 是一个全局变量,用于保存最近一次系统调用或库函数调用失败的错误码。errno 定义在 errno.h 头文件中。

在 Linux 中,fcntl 是一个用于对已打开的文件描述符进行各种操作的系统调用。fcntl 可以用于设置文件描述符的属性、锁定文件以及获取文件状态等。fcntl 的原型如下:

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */);

其中,fd 表示文件描述符,cmd 表示需要执行的命令,arg 表示命令参数。fcntl 函数的返回值取决于所执行的命令。

fcntl 的一些常用命令如下:

  • F_DUPFD:复制文件描述符。
  • F_GETFD:获取文件描述符标志。
  • F_SETFD:设置文件描述符标志。
  • F_GETFL:获取文件状态标志。
  • F_SETFL:设置文件状态标志。
  • F_GETLK:获取文件锁。
  • F_SETLK:设置文件锁。
  • F_SETLKW:设置文件锁(阻塞方式)。

在使用 fcntl 函数时,需要注意以下几点:

  • fcntl 函数可以对任何打开的文件描述符进行操作,包括标准输入、标准输出、网络套接字等。
  • fcntl 函数的返回值与命令有关,一般情况下返回 0 表示执行成功,否则返回 -1,并设置 errno。
  • 命令参数 arg 可以是一个指针,指向一个与命令有关的结构体,用于传递更多的参数。
  • 在使用 fcntl 函数设置文件描述符属性时,需要用 OR 操作将属性值添加到原有的属性值中。
  • 在使用 fcntl 函数设置文件锁时,需要使用 F_SETLK 和 F_SETLKW 命令,分别表示设置非阻塞锁和阻塞锁。

常用的 fcntl 命令包括 F_GETFL 和 F_SETFL,用于获取和设置文件状态标志。这些标志包括 O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)、O_APPEND(追加)、O_CREAT(如果文件不存在则创建)、O_TRUNC(截断文件)、O_EXCL(在创建文件时检查文件是否存在)等。这些标志可以通过 OR 操作组合使用,例如 O_RDWR | O_CREAT 表示以读写模式打开文件,并在文件不存在时创建文件。

在 Linux 中,fcntl 函数是一个系统调用,可以直接在用户程序中调用。一些高级语言(如 C++)也提供了对 fcntl 函数的封装,可以通过库调用的方式使用。但是,需要注意的是,在使用库调用时,需要按照库的规范进行参数传递等操作,以避免出现错误。

3.2 多路转接

多路转接和IO复用是两个相关但不完全相同的概念。

IO复用是一种异步IO模型,它允许一个进程同时监视多个文件描述符(包括socket、标准输入输出等),并在有数据可读或可写时进行相应的处理,而不需要阻塞等待。常用的IO复用函数包括 select、poll、epoll 等。

多路转接(Multiplexing)是一种实现IO复用的技术,它允许一个进程同时监听多个网络连接(如多个客户端连接),并在有数据可读或可写时进行相应的处理。多路转接的实现方式包括多线程、多进程、多路复用等。其中,多路复用就是一种基于IO复用的多路转接方式,它利用 IO 复用函数来同时监听多个文件描述符的事件,并在有事件发生时进行相应的处理。

因此,IO复用和多路转接是相关的概念,可以互相转化和结合使用。在 Linux 系统中,通常使用 IO 复用函数来实现多路转接,如使用 epoll 函数来同时监听多个客户端连接事件,并在有数据可读或可写时进行相应的处理。

3.2.1 select

在这里插入图片描述

3.2.1.1 接口说明

在 Linux 系统中,select 是一种 IO 复用函数,用于同时监听多个文件描述符(包括 socket、标准输入输出等)的事件,并在有数据可读或可写时进行相应的处理。其原型如下:

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

其中,参数 nfds 表示文件描述符集合中最大描述符值加 1;参数 readfds、writefds 和 exceptfds 分别表示可读、可写和异常事件的文件描述符集合;参数 timeout 表示 select 函数的超时时间,若为 NULL 则表示无限等待。

以readfds为例
作用:所有关心读事件的文件描述符,都应该添加在这个集合中。
输入:用户告诉内核,os你要帮我检测一下在这个集合中的fd的读事件
输出:内核告诉用户,你关心的fd,有哪些文件描述符已经就绪了,可以读取了。

3.2.1.2 使用流程和注意事项

select 函数的使用流程如下:

  1. 首先需要清空文件描述符集合,并将需要监听的文件描述符添加到对应的集合中,如下所示:
fd_set rfds; // 可读文件描述符集合
FD_ZERO(&rfds); // 初始化为空集合
FD_SET(sockfd, &rfds); // 将 sockfd 添加到可读集合中

FD_SET 是一个宏,定义在 sys/select.h 头文件中,用于将指定的文件描述符添加到文件描述符集合中。其原型如下:

void FD_SET(int fd, fd_set *set);

其中,参数 fd 表示要添加的文件描述符,参数 set 表示要添加到的文件描述符集合。

文件描述符集合是一个位图结构,每个文件描述符对应集合中的一个位。因此,在使用 FD_SET 函数时,实际上是把 fd 对应的位设置为 1,从而表示该文件描述符已经被添加到集合中

fd_set中,比特位的位置,代表文件描述符编号,从低位到高位,0,1,2……
比特位的内容代表是否关心(读等事件)

需要注意的是,文件描述符集合有一定的大小限制,通常为 1024 或 2048 个位。因此,在使用 FD_SET 函数时需要特别注意集合大小的设置,以防止超出限制。fd_set是一种具体的类型,在具体平台上,该类型的大小是确定的。所以select只能够进行检测确定数量的文件描述符。

并且参数set是一个输入输出型参数,它的弊端在于:使用同一个变量,每次调用返回的时候,原来的参数会被返回值覆盖,再要传参时,都要进行重新设置。

另外,使用 FD_SET 函数添加文件描述符到集合中后,需要使用 FD_ISSET 函数来检查文件描述符是否有事件发生,如下面第三部分所示。

  1. 然后调用 select 函数进行监听,并设置超时时间,如下所示:
struct timeval tv; // 超时时间
tv.tv_sec = 5; // 秒
tv.tv_usec = 0; // 微秒
int ret = select(sockfd + 1, &rfds, NULL, NULL, &tv); // 监听可读事件
  1. 在 select 函数返回后,需要检查各个文件描述符集合中的文件描述符是否有事件发生,如下所示:
if (ret == -1) {
    perror("select");
} else if (ret == 0) {
    printf("timeout\n");
} else {
    if (FD_ISSET(sockfd, &rfds)) {
        // sockfd 有数据可读
        // ...
    }
}

需要注意的是,select 函数在监听文件描述符时,会阻塞当前进程直到有事件发生或超时。因此,在使用 select 函数时需要特别注意超时时间的设置,以防止进程长时间阻塞。

另外,select 函数的效率较低,对文件描述符进行遍历比较耗时,而且一次只能监听有限数量的文件描述符。因此,在 Linux 系统中,通常使用更高效的 IO 复用函数,如 epoll 函数,来实现多路复用。

3.2.1.3 select 服务器端

核心框架伪代码

while(1)
{
	int fd_array[];
	for()	;	//添加所有的sock到fd_set集合中
	maxfd;

	select();
	
	for();	//根据fd_array[]检测哪些sock已经就绪
	add sock to fd_array[];	//for();
	read/write/error
}

具体代码

利用多路转接之select写的服务端

3.2.1.4 select 缺点

每次调用select,都需要手动设置fd集合,从接口使用角度来说也非常不便,
每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在id很多时也很大
select支持的文件描述符数量太小.

3.2.2 poll

在这里插入图片描述

3.2.2.1 接口说明

在Linux系统中,poll()是一种用于异步I/O操作的系统调用,它可以让程序等待多个文件描述符上的I/O事件。poll()函数会一直阻塞直到有一个或多个文件描述符上有I/O事件发生或者超时。

poll()函数的参数如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

其中,fds是一个指向结构体数组的指针,每个结构体描述了一个文件描述符及其关注的事件类型。nfds是fds中结构体的数量。timeout是超时时间,单位是毫秒。

pollfd结构体的定义如下:

struct pollfd {
    int fd;          // 文件描述符
    short events;    // 定义关注的事件类型(如 POLLIN、POLLOUT 等)
    short revents;   // 实际发生的事件类型
};
3.2.2.2 使用流程和注意事项

用法如下:

  1. 调用poll()函数前,需要将要关注的文件描述符及其对应的事件类型填入fds结构体数组中。
  2. 调用poll()函数,等待I/O事件。
  3. 当poll()函数返回时,可以通过遍历fds数组获取每个文件描述符发生的事件类型,即revents字段的值。

poll()函数的返回值表示有多少个文件描述符发生了事件,或者在超时时间内没有任何事件发生。返回值为负数表示出错。

**poll()函数的优点是可以同时监听多个文件描述符,而且不需要像select()函数一样需要不断地重新构造fd_set结构体。**但是,当关注的文件描述符数量较多时,poll()函数的性能会有所下降。

timeout 设置为0时代表非阻塞轮询,设置为-1时代表阻塞等待,设置为大于0的数代表等待时间。

3.2.2.3 poll 服务器端

基于多路转接之poll接口写的服务器

3.2.2.4 poll的缺点

poll中监听的文件描述符数目增多时
(1)和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符
(2)每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中。
(3)同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降.

3.2.3 epoll

在这里插入图片描述

在这里插入图片描述

3.2.3.1 接口说明

在Linux系统中,epoll是一种高效的I/O多路复用机制,它可以让程序同时处理多个文件描述符上的I/O事件。相比于select和poll,epoll在处理大量文件描述符时的性能更好。

epoll使用三个系统调用:epoll_createepoll_ctlepoll_wait。头文件为#include <sys/epoll.h>

  1. epoll_create:创建一个epoll实例,返回一个文件描述符(epoll_fd)来指向创建的epoll实例。
int epoll_create(int size);

其中,size参数表示epoll实例中可以关注的文件描述符的最大数量。

  1. epoll_ctl:注册或者注销一个文件描述符。
int epoll_ctl(int epoll_fd, int op, int fd, struct epoll_event *event);

其中,epoll_fd是epoll实例的文件描述符,fd是待注册或注销的文件描述符,op可以取EPOLL_CTL_ADD(注册)、EPOLL_CTL_MOD(修改)和EPOLL_CTL_DEL(注销)。

op分别为:
(1)注册一个文件描述符fd,到epfd中。
(2)修改已经注册的fd中的监听事件。
(3)从epfd中删除一个fd

event是一个epoll_event结构体,表示fd关注的事件类型。

  1. epoll_wait:等待一个或多个文件描述符上的I/O事件。
int epoll_wait(int epoll_fd, struct epoll_event *events, int maxevents, int timeout);

其中,epoll_fd是epoll实例的文件描述符,events是一个指向epoll_event结构体数组的指针,表示发生I/O事件的文件描述符和事件类型,maxevents是events数组中元素的数量,timeout是等待超时时间,单位是毫秒。当一个或多个文件描述符上发生I/O事件时,epoll_wait返回,返回值表示发生I/O事件的文件描述符数量。

  • epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。
  • maxevents告知内核这个events有多大,这个maxevents的值不能大于创建epoll_create时的size
  • 参数timeout是超时时间(毫秒,0会立即返回,-1是永久阻塞).
  • 如果函数调用成功,返回对应IO上已准备好的文件描述符数目,如返回0表示已超时,返回小于0表示函数失败

epoll_event结构体的定义如下:

typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t events;    // 定义关注的事件类型(如 EPOLLIN、EPOLLOUT 等)
    epoll_data_t data;  // 用户数据
};

epoll的优点在于:

  1. 高效:epoll使用红黑树和链表数据结构管理关注的文件描述符,能够快速地响应大量文件描述符上的I/O事件。
  2. 只关注发生了事件的文件描述符:epoll_wait仅会返回发生I/O事件的文件描述符,而不是返回所有注册的文件描述符。
  3. 与非阻塞I/O配合使用:epoll可以与非阻塞I/O配合使用,提高程序的性能。

epoll的一些缺点:

  1. 对代码的复杂度要求高:epoll的使用需要对I/O模型和事件驱动编程有一定的了解,对代码的复杂度要求较高。
  2. 只能在Linux中使用:epoll是Linux特有的机制,不支持在其他操作系统中使用。
3.2.3.2 epoll 的工作原理

在这里插入图片描述

3.2.3.3 epoll 服务器端

基于多路转接之epoll写的服务器

3.2.3.4 epoll 的工作方式

工作方式是指:底层有数据之后,OS向上层进行通知的方式。
比如,当tcp的接收缓冲区中有数据时,也会存在不向上通知的情况。
(1)LT:水平触发
只要底层有数据,就会向上层一直通知,直到上层将数据全部取完。
即便时上层没有吧数据全部取走,我们也不担心,因为还有下一次机会。
(2)ET:边缘触发
只有底层数据,从无到有从有到多的时候,会向上层进行通知就绪,否则一律不通知。即便上层取走了数据,可能还是有残留。
这就使得其通知有效次数居多。
倒逼上层要取,就一定要将自己本次的数据全部取走,将报文尽快成型,上层报文尽快被处理。

水平触发就像是:一个快递员拿着你网购的三个快递,打电话让你来取,你却迟迟不来,快递员就反复打电话催你。
边缘触发就像是:一个快递员催你拿快递,你迟迟不来的时候他也不打电话,自己忙活别的去,当他又收到你另外的几个快递,还会给你打电话,告诉你再不来我就走了,倒逼你去取完。

ET模式下,会倒逼程序员取走所有就绪数据,但程序员不能立即知道底层还剩多少数据。但是可以进行尝试,进行循环读取,一直到读取返回值告诉我们没有数据了!
有可能碰到,前几次读全部满足,只不过最后一次已经把底层读完了(但是我们不知道),后续一定会再read,此时read会被阻塞挂起!导致整个服务被挂起!所以在ET下,读取必须是非阻塞的读取!

基于ET模式的epoll就是Reactor
底层一旦有数据,我们上层就能立马进行处理,并且不用我们主动等待。
就像打地鼠一样。

单进程基于ET模式实现的一个Reactor模式,并实现了在线加法器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/6135.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能从来不是一帆风顺的

不久之前&#xff0c;人们还常说&#xff0c;计算机视觉的辨别能力尚不如一岁大的孩子。如今看来&#xff0c;这句话要改写了。 ----- 特伦斯谢诺夫斯基 目录 人工智能初现端倪 人工智能初现 人工智能“第一次危机”——“设计派”行不通 第一次危机破局——学习派初见成效…

Python-np.expand_dims()

1. np.expand_dims 用于扩展数组的维度 执行程序后注意观察中括号[ ]的位置和数量 np.expand_dims(a, axis0)表示在axis0维度处扩展维度&#xff0c;加一层中括号[ ]; np.expand_dims(a, axis1)表示在axis1维度处扩展维度&#xff0c;加一层中括号[ ]; np.expand_dims(a, …

Golang每日一练(leetDay0025)

目录 73. 矩阵置零 Set Matrix Zeroes &#x1f31f;&#x1f31f; 74. 搜索二维矩阵 Search A 2d-Matrix &#x1f31f;&#x1f31f; 75. 颜色分类 Sort Colors &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Pyth…

大数据领域的发展及其对现实世界的价值

大数据已经成为全球各行业领域不可或缺的一部分&#xff0c;并且其应用不断涌现。尽管很多人最初对“大数据”这一术语表示怀疑和不信任&#xff0c;但大数据技术已经确立了稳定的发展方向。根据调研机构的预测&#xff0c;到2027年&#xff0c;全球大数据市场规模将达到1090亿…

PostgreSQL之Full VACUUM

前面我们介绍了PG中的Concurrent VACUUM&#xff0c;详细请参考PostgreSQL之Concurrent VACUUM&#xff0c;这篇我们继续来了解一下PG中的Full VACUUM。 我们了解Concurrent VACUUM允许在执行的时候仍然允许对正在VACUUM的表进行读取操作&#xff0c;因此这个操作对业务的影响也…

学成在线:第五天(p82-p93)

1、面试&#xff1a;在分片广播中&#xff0c;如何保证多个执行器不会重复执行任务&#xff1f; &#xff08;虽然有分片序号&#xff0c;但是加入只有两个执行器&#xff0c;有很多任务&#xff0c;这两个执行器得到的分片序号也就只是两个数字&#xff0c;后面的分片是如何能…

蓝桥杯刷题冲刺 | 倒计时11天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.质因子2.蓝桥王国1.质因子 题目 链接&#xff1a; 1545. 质因子 - AcWing题库 给定一个整数 N…

NexNoSQL Client:Elasticsearch、Redis、MongoDB三合一的可视化客户端管理工具

背景&#xff1a; 工作中我们使用了Elasticsearch作为存储&#xff0c;来支持内容的搜索&#xff0c;Elasticsearch这个软件大家都耳熟能详&#xff0c;它是一个分布式、高扩展、高实时的搜索与数据分析引擎&#xff0c;不仅仅支持文本索引&#xff0c;还支持聚合操作&#xf…

Java面向对象部分 个人学习记录

注:此博客是个人学习记录&#xff0c;会有错的地方&#xff0c;面向对象部分我可能会画很多图来加深我的理解 不引出了&#xff0c;直接开始 class Dog{String name;int age;String type;public Dog(String name,int age,String type){this.namename;this.ageage;this.typetyp…

全志V3S嵌入式驱动开发(准备)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 要学好嵌入式&#xff0c;其实最好自己可以画板子、调试板子、写软件、调试软件这样来学习。简单的51单片机、stm32这样的mcu或许画起来不是很复杂…

Python 自动化指南(繁琐工作自动化)第二版:零、前言

关于作者 Al Sweigart 是一名软件开发人员和技术书籍作者。Python 是他最喜欢的编程语言&#xff0c;他是该语言的几个开源模块的开发者。他的其他书籍可以在他的网站上根据知识共享许可免费获得。他的猫现在重 11 磅。 关于技术评审 Philip James 从事 Python 工作已经超过…

MobTech 秒验|本机号码一键登录会泄露隐私吗

本机号码一键登录是一种新型的应用登录方式&#xff0c;它可以利用运营商的数据网关认证能力&#xff0c;实现手机号免密登录&#xff0c;提高用户体验和转化率&#xff0c;降低验证成本和流失率。本机号码一键登录支持三大运营商号码认证&#xff0c;3秒内完成手机号验证&…

Java小课堂:自定义注解(案例:自定义DecimalFormat注解)

文章目录 引言I 预备知识1.1 元注解1.2 Target注解的ElementType枚举1.3 Retention注解的RetentionPolicy枚举II 自定义注解2.1 基本条件2.2 注解自定义属性的格式III 案例3.1 自定义DecimalFormat注解3.2 自定义json序列化解析引言 需求: 编辑费率限制的值时填写几位就保存几…

如何在前端中实现精美的图片和文字的垂直对齐

&#x1f31f;所属专栏&#xff1a;前端只因变凤凰之路 &#x1f414;作者简介&#xff1a;rchjr——五带信管菜只因一枚 &#x1f62e;前言&#xff1a;该系列将持续更新前端的相关学习笔记&#xff0c;欢迎和我一样的小白订阅&#xff0c;一起学习共同进步~&#x1f449;文章…

关于线性与条带化下LVM增加磁盘数据分布的讨论

关于线性与条带化下LVM增加磁盘数据分布的讨论一、环境介绍二、线性模式逻辑卷创建逻辑卷删除三、条带化逻辑卷创建逻辑卷删除LVM有两种模式&#xff1a;线性模式&#xff1a;先写满组成线性逻辑卷的第一个物理卷&#xff0c;再向第2个物理卷中写入数据&#xff0c;以此类推&am…

必看>>>>Linux数据库被其他服务器远程访问(修改权限、开设端口)

目录 一&#xff1a;修改权限 1.1 进入Linux数据库 1.2 修改数据库的远程连接权限 1.2.1 数据库远程权限修改命令 1.2.2 数据库远程权限查看命名 1.3 给Linux机添加端口 1.4 远程数据库连接 注意mysql中的中英文输入 一&#xff1a;修改权限 1.1 进入Linux数据库 文章…

【职场热点】软件测试岗位,真正达到月收入1万以上的有多少

在某一线城市相亲角&#xff0c;有位阿姨自称孩子是程序员、36岁、月入3万8&#xff0c;并直言“月入1万如讨饭”&#xff0c;引来大量互联网人自嘲式认领“乞丐”身份。今年2月&#xff0c;“月收入一万”的鄙视链压力给到了二线城市&#xff0c;有位入行2年的软件测试员发帖称…

1-python基础

1.字面量 被写在代码中固定的值 2.注释 # 单行注释""" 多行注释1&#xff1a;三对双引号 """ 多行注释2&#xff1a;三对单引号 3.变量与输出 age10 # 变量赋值 print("我今年",age,"岁") &#xff08;1&#xff09;pri…

Java数据类型与变量

1.字面常量 public class Demo{ public static void main(String[] args){ System.Out.println("hello world!"); System.Out.println(300); System.Out.println(3.1415926); System.Out.println(T); System.Out.println(true); System.Out.println(false); } 其中…

【CocosCreator入门】CocosCreator组件 | ParticleSystem (粒子)组件

Cocos Creator 是一款流行的游戏开发引擎&#xff0c;具有丰富的组件和工具&#xff0c;其中ParticleSystem组件是一个用于创建粒子效果的工具&#xff0c;可用于实现火花、爆炸、雪花等效果。 目录 一、组件属性 二、组件使用 三、效果演示 四、性能优化 一、组件属性 属性…