とんぼの気持ちとんぼの気持ち
Home
  • Spring Cloud

    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Cloud Netflix
  • Zookeeper

    • Zookeeper
  • 分布式锁

    • 分布式锁
  • 分布式事务

    • 分布式事务
GitHub
Home
  • Spring Cloud

    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Cloud Netflix
  • Zookeeper

    • Zookeeper
  • 分布式锁

    • 分布式锁
  • 分布式事务

    • 分布式事务
GitHub

阻塞式IO(blocking IO)

在阻塞式I/O模型中,当程序执行一个I/O操作时,它会一直等待直到该操作完成后才会继续执行后续的代码。这种模型的优点是简单易懂,但是会造成系统资源浪费和响应时间延迟

同步阻塞IO

在同步阻塞I/O模型中,当程序调用I/O操作时,它会被阻塞,直到I/O操作完成后才会返回。这种模型的实现比较简单,但是会造成程序响应时间的延迟。在处理大量并发连接时存在效率问题,因为每个连接都需要独立的线程或进程进行阻塞式I/O操作,会占用大量的系统资源和导致线程或进程切换的开销。因此,在高并发场景下,通常会使用异步非阻塞I/O等方式来提高系统的性能和效率。

传统阻塞式I/O

程序使用系统调用(如read、write等)阻塞等待I/O操作的完成,直到操作完成或发生错误后才返回结果。

select/poll模型

程序使用select或poll函数阻塞等待多个I/O事件的完成,直到其中一个事件发生或超时后才返回结果。

同步阻塞套接字I/O

程序使用套接字阻塞等待I/O操作的完成,直到操作完成或发生错误后才返回结果。

多线程阻塞IO

在多线程阻塞I/O模型中,当程序调用I/O操作时,它会创建一个新的线程来处理该I/O操作。这种模型可以避免阻塞主线程,但是会造成线程的开销和上下文切换的开销。多线程阻塞I/O在处理大量并发连接时会存在线程切换等开销问题,因此需要合理设计和优化线程数、线程池大小等参数,避免造成系统负载过高和性能下降的问题。

线程池

程序通过创建线程池来处理I/O操作,当有新的I/O任务时,线程池会选择一个空闲的线程来处理任务。线程池可以避免频繁创建和销毁线程的开销,提高系统的效率和性能。

多线程I/O模型

程序使用多个线程来处理I/O操作,每个线程负责处理一个或多个连接的I/O操作。当有新的连接到来时,程序会创建一个新的线程来处理该连接的I/O操作。多线程I/O模型可以实现高并发的处理能力,但也会存在线程切换等开销的问题。

基于事件驱动的多线程I/O模型

程序使用多个线程来处理I/O操作,每个线程负责监听一组事件。当事件发生时,程序会通知对应的线程进行处理。基于事件驱动的多线程I/O模型可以实现高并发和高效率的处理能力。

多进程阻塞IO

在多进程阻塞I/O模型中,当程序调用I/O操作时,它会创建一个新的进程来处理该I/O操作。这种模型可以避免阻塞主进程,但是会造成进程的开销和资源的浪费,多进程阻塞I/O在处理大量并发连接时会存在进程切换等开销问题,因此需要合理设计和优化进程数、进程池大小等参数,避免造成系统负载过高和性能下降的问题。

多进程I/O模型

程序使用多个进程来处理I/O操作,每个进程负责处理一个或多个连接的I/O操作。当有新的连接到来时,程序会创建一个新的进程来处理该连接的I/O操作。多进程I/O模型可以实现高并发的处理能力,但也会存在进程切换等开销的问题。

prefork模型

程序在启动时创建多个子进程,每个子进程负责处理一个或多个连接的I/O操作。当有新的连接到来时,程序会将该连接分配给空闲的子进程来处理。prefork模型可以避免进程创建和销毁的开销,提高系统的效率和性能。

信号驱动阻塞IO

在信号驱动阻塞I/O模型中,程序会注册一个信号处理函数来处理I/O操作完成后的信号。当I/O操作完成后,操作系统会发送一个信号给程序,程序通过信号处理函数来处理该I/O事件。这种模型可以避免阻塞主线程或进程,但是由于信号传递的开销,会降低程序的性能,使用信号驱动阻塞I/O时需要特别小心,因为信号的处理可能会引入竞争条件和不可预期的行为。在处理信号时,需要避免使用非可重入函数和全局变量等可能引起线程安全问题的操作,同时需要使用信号屏蔽和信号处理函数来确保信号处理的正确性和可靠性。

SIGIO

程序通过设置SIGIO信号来通知有新的I/O事件发生,然后程序可以调用相应的I/O操作函数来处理事件。SIGIO信号可以使用fcntl函数设置,以及使用异步I/O函数如aio_read, aio_write等。

SIGURG

当socket接收到带外数据时,会产生SIGURG信号,程序可以通过设置SIGURG信号来处理带外数据。程序可以使用fcntl函数设置接收带外数据的条件,以及使用recv函数等函数接收带外数据。

多路复用阻塞IO(select/poll)

在select/poll阻塞I/O模型中,程序使用select或者poll函数来监听多个I/O事件,并阻塞等待这些事件的发生,因此它们是阻塞式I/O模型。当其中一个事件发生后,程序会立即处理该事件。这种模型可以同时处理多个I/O事件,但是需要额外的编程复杂度。需要注意的是,select和poll存在一些缺点,例如轮询效率低,最大连接数有限等问题。


非阻塞式IO(nonblocking IO)

在非阻塞式I/O模型中,程序会通过轮询的方式查询I/O操作的状态,如果I/O操作已经完成,程序会立即继续执行后续的代码。这种模型的优点是可以减少系统资源的浪费和响应时间的延迟,但是需要频繁的轮询操作会消耗CPU资源

同步非阻塞IO

在同步非阻塞I/O模型中,程序调用I/O操作后,会立即返回,然后通过轮询方式查询I/O操作是否已经完成。这种模型需要频繁的轮询操作会消耗CPU资源,但是可以减少响应时间的延迟,同步非阻塞I/O相比异步I/O的效率和性能要略低,因为程序需要轮询文件描述符的状态,以判断是否可以进行I/O操作,而轮询的开销比较大。此外,在多线程和多进程环境下,使用非阻塞I/O需要注意线程安全和进程安全问题,避免出现竞争条件和不一致性等问题

非阻塞I/O

程序通过使用O_NONBLOCK选项来设置非阻塞I/O模式,当进行I/O操作时,如果没有立即得到结果,则立即返回而不是等待,从而实现异步I/O操作的效果。

可读性/可写性I/O

程序可以使用select或poll等系统调用来检查文件描述符的状态,以确定是否可以读取或写入数据,从而实现异步I/O操作的效果。

异步非阻塞IO

在异步非阻塞I/O模型中,程序调用I/O操作后,会立即返回,并注册一个回调函数来处理I/O操作的完成。当I/O操作完成后,操作系统会通知程序,并由程序处理该I/O事件。这种模型可以最大化利用系统资源,减少系统响应时间的延迟,但是由于需要额外的编程复杂度,使用起来比较困难,异步非阻塞I/O的实现比较复杂,需要程序员具备较高的技术水平,同时需要注意异步I/O的使用方式和异步I/O的特性,以避免出现数据竞争、内存泄漏等问题。

aio_read/aio_write

程序可以使用aio_read和aio_write等异步I/O操作函数来进行I/O操作,这些函数会立即返回,并且当I/O操作完成后,系统会发送信号通知程序I/O操作已经完成,从而实现异步I/O操作的效果。

epoll

epoll是一种高效的I/O多路复用机制,它使用内核事件驱动机制来监控文件描述符的状态,并通知程序有哪些文件描述符可以进行I/O操作,从而实现异步I/O操作的效果。

多路复用非阻塞IO

在多路复用非阻塞I/O模型中,程序使用select(select通常与非阻塞IO一起使用)或者epoll等函数监听多个I/O事件,当其中一个事件完成时,程序会立即处理该事件。这种模型可以同时处理多个I/O事件,减少系统资源的浪费和响应时间的延迟,但是需要额外的编程复杂度。


IO多路复用(IO multiplexing)

在I/O复用模型中,程序使用select或者epoll等函数监听多个I/O事件,当其中一个事件完成时,程序会立即处理该事件。这种模型的优点是可以同时处理多个I/O事件,减少系统资源的浪费和响应时间的延迟,但是需要额外的编程复杂度

Linux 三种IO多路复用机制

Select

select() 是最古老、最通用的一种 I/O 多路复用机制,它可以同时监控多个文件描述符的可读和可写状态,并在其中任何一个文件描述符就绪时返回。但是 select() 存在文件描述符数量限制和效率较低的问题,通常只适用于小规模的网络应用。

select() 的使用

select() 的使用方法比较简单,主要包括以下三个步骤:

  1. 调用 select() 函数注册文件描述符

在使用 select() 函数之前,需要先将需要监控的文件描述符注册到 select() 中。可以使用 fd_set 类型的数据结构将需要监控的文件描述符集合进行描述,并通过 select() 函数将其注册到内核中。

fd_set read_fds; // 可读文件描述符集合
fd_set write_fds; // 可写文件描述符集合
fd_set except_fds; // 异常文件描述符集合
int max_fd = -1; // 最大文件描述符数
int timeout = 5000; // 超时时间
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&except_fds);
FD_SET(fd1, &read_fds); // 将 fd1 加入可读文件描述符集合
FD_SET(fd2, &write_fds); // 将 fd2 加入可写文件描述符集合
FD_SET(fd3, &except_fds); // 将 fd3 加入异常文件描述符集合
max_fd = fd3; // 更新最大文件描述符数
int ret = select(max_fd + 1, &read_fds, &write_fds, &except_fds, timeout);
  1. 调用 select() 函数等待事件

select() 函数会阻塞进程,等待文件描述符集合中任何一个文件描述符的事件就绪。如果没有事件发生,select() 函数会一直阻塞,直到超时或者有事件发生才会返回。

int ret = select(max_fd + 1, &read_fds, &write_fds, &except_fds, timeout);
if (ret == -1) {
    perror("select error");
} else if (ret == 0) {
    printf("select timeout\n");
} else {
    if (FD_ISSET(fd1, &read_fds)) {
        // fd1 可读事件就绪
    }
    if (FD_ISSET(fd2, &write_fds)) {
        // fd2 可写事件就绪
    }
    if (FD_ISSET(fd3, &except_fds)) {
        // fd3 异常事件就绪
    }
}
  1. 处理文件描述符事件

如果 select() 函数返回值大于 0,说明至少有一个文件描述符就绪,可以使用 FD_ISSET() 宏函数判断哪个文件描述符就绪,并进行相应的处理。 select() 函数的效率较低,主要原因是每次调用 select() 函数都需要将文件描述符集合从用户态复制到内核态,同时返回结果时也需要进行反复的复制操作。另外,select() 函数的文件描述符数量也存在一定的限制,通常只适用于小规模的网络应用。

Poll

poll() 与 select() 类似,但是没有文件描述符数量限制,且效率较高。它也可以同时监控多个文件描述符的可读和可写状态,并在其中任何一个文件描述符就绪时返回。

Epoll

epoll() 是 Linux 下最常用的 I/O 多路复用机制,它是 Linux 2.6 内核引入的新机制。epoll() 支持多个文件描述符的监控,并具有较高的效率和稳定性,特别是在大规模的网络应用中表现更为出色。 epoll() 采用事件驱动机制,允许应用程序使用 epoll_ctl() 函数向内核注册文件描述符的事件,并使用 epoll_wait() 函数等待事件的发生。当文件描述符的事件就绪时,内核会通知应用程序,从而实现高效的 I/O 处理。与 select() 和 poll() 相比,epoll() 具有更好的可扩展性和性能。

Windows下能使用 epoll 吗

Windows 操作系统不支持 epoll,因为 epoll 是 Linux 系统提供的一种 I/O 多路复用机制,而 Windows 使用的是自己的 I/O 多路复用机制。Windows 下的主要 I/O 多路复用机制是 select() 和 I/O Completion Ports (IOCP)。 如果需要在 Windows 平台上使用 epoll,可以考虑使用跨平台的网络库,例如 Boost.Asio、Libevent 或者 Mongoose 等,这些库提供了跨平台的网络编程接口,并针对不同的操作系统选择了适当的 I/O 多路复用机制,使得应用程序在不同平台上都能够达到较高的性能表现。

文件描述符

文件描述符(File Descriptor)是 Linux 下一种用于表示打开的文件或者 I/O 设备的抽象概念,它是一个非负整数。在程序中,通过文件描述符来访问打开的文件或者设备,进行读写等操作。 在 Linux 中,一切皆文件,包括硬件设备、网络连接、管道、套接字等等。每个文件都有一个唯一的文件描述符来表示它,在程序中打开文件时,内核会返回一个对应的文件描述符,程序可以使用这个文件描述符来对文件进行操作。 例如,程序通过 open() 函数打开一个文件时,会返回一个文件描述符:

int fd = open("test.txt", O_RDWR);

在上面的代码中,open() 函数会打开 test.txt 文件,并返回一个文件描述符 fd,程序可以通过 fd 来进行读写等操作。当不再需要访问该文件时,程序需要使用 close() 函数关闭文件描述符:

close(fd);

在使用 I/O 多路复用机制时,通常需要将多个文件描述符注册到 select() 或 epoll() 中,以便同时监控多个文件描述符的可读和可写状态。

Nginx中的网络请求也可以用文件描述符表示吗

nginx 中的网络请求也可以使用文件描述符来表示。在 nginx 中,每个连接对应一个文件描述符,它被用于读写网络数据。当有新的客户端连接时,nginx 会为该连接创建一个文件描述符,并将其添加到 epoll 实例中进行监控。当该连接上有数据可读或可写时,nginx 就会从 epoll 实例中获取该文件描述符,并进行相应的读写操作。 例如,在 nginx 的网络模块中,可以看到如下的代码:

ngx_event_t *rev = c->read;
ngx_event_t *wev = c->write;

/* 获取文件描述符 */
ngx_socket_t fd = c->fd;

/* 添加到 epoll 实例中进行监控 */
if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT) != NGX_OK) {
    ngx_close_connection(c);
    return;
}

if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_CLEAR_EVENT) != NGX_OK) {
    ngx_close_connection(c);
    return;
}

/* 监听可读和可写事件 */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
    ngx_close_connection(c);
    return;
}

if (ngx_handle_write_event(wev, 0) != NGX_OK) {
    ngx_close_connection(c);
    return;
}

在上面的代码中,c->fd 表示该连接的文件描述符,ngx_add_event() 函数会将该文件描述符添加到 epoll 实例中进行监控。当连接上有数据可读或可写时,nginx 会调用相应的读写处理函数进行数据处理。

Windows 两种 I/O 多路复用机制

Select

与 Linux 下的 select() 机制类似,Windows 也提供了 select() 函数用于多路复用。select() 函数允许一个进程同时监视多个文件描述符的可读和可写状态,并在其中任何一个文件描述符就绪时返回。

I/O Completion Ports (IOCP)

I/O Completion Ports (IOCP) 是 Windows 下的一种高性能的 I/O 多路复用机制。IOCP 是一种事件驱动机制,采用异步 I/O 模型实现。IOCP 可以监控多个文件描述符的可读和可写状态,并在其中任何一个文件描述符就绪时通知应用程序,从而实现高效的 I/O 处理。与 select() 相比,IOCP 有更好的性能和可扩展性,特别是在高并发的场景下表现更为出色。 需要注意的是,IOCP 是 Windows 独有的机制,不同于 Unix-like 系统上的 select()、poll()、epoll()、kqueue() 等机制。因此,在开发跨平台应用程序时需要特别注意这些差异。

BSD系统

kqueue

是BSD系统提供的一种I/O多路复用机制,与epoll()类似,但是更加灵活和高效


信号驱动IO(signal driven IO)

在信号驱动式I/O模型中,当I/O操作完成后,操作系统会向程序发送一个信号,程序通过捕获信号的方式处理I/O事件。这种模型的优点是可以避免频繁的轮询操作,但是由于信号传递的开销,会降低程序的性能,信号驱动I/O具有一定的局限性,例如它不能处理多个文件描述符的I/O操作,而且对于每次I/O操作都需要重新设置信号处理函数,同时也需要注意信号处理函数的安全性和可重入性。因此,对于大规模、高并发的I/O应用场景,多路复用机制和异步I/O操作更为常见和推荐。

SIGIO信号

程序可以使用fcntl函数将文件描述符设置为非阻塞模式,并且使用F_SETOWN命令将该文件描述符的所有者设置为当前进程。当该文件描述符上的I/O操作完成时,内核会发送一个SIGIO信号通知进程,从而实现信号驱动I/O的效果。

SIGURG信号

当一个套接字接收到带外数据时,内核会向进程发送一个SIGURG信号,程序可以使用该信号来实现信号驱动I/O。


异步IO(asynchronous IO)

在异步I/O模型中,程序发起一个I/O操作后,立即返回,并继续执行后续的代码。当I/O操作完成后,操作系统会通知程序,并由程序处理该I/O事件。这种模型的优点是可以最大化利用系统资源,减少系统响应时间的延迟,但是由于需要额外的编程复杂度,使用起来比较困难。

Callback-based(基于回调函数)

使用回调函数来实现异步操作,当操作完成时会调用预先定义好的回调函数。

Promise-based(基于Promise)

Promise是一种表示异步操作最终结果的对象,可以在异步操作完成后返回结果或错误。

Async/await-based(基于async/await)

这是一种使用异步函数的方式,它可以在函数内使用await关键字等待异步操作的结果,并在操作完成后返回结果。

Event-driven(基于事件驱动)

通过注册事件处理程序来响应异步操作的完成事件,当操作完成时会触发相应的事件,事件处理程序会执行相应的操作。

Coroutine-based(基于协程)

使用协程实现异步操作,可以在异步操作执行期间挂起协程,并在操作完成后恢复协程执行。协程是一种轻量级线程,可以在单个线程中运行多个并发任务。


其他

网络请求也是IO操作吗

网络请求也是I/O操作,因为它涉及到数据在计算机系统中的输入和输出。网络请求需要通过计算机系统的网络接口与外部网络通信,发送数据并接收响应,这些过程都需要进行输入和输出操作。在进行网络请求时,程序需要等待数据的返回,这种等待也属于I/O操作中的一种,因为程序需要等待外部系统的响应才能继续执行后续的操作。因此,网络请求也被视为I/O操作的一种。

CPU密集型程序有必要使用epoll模型吗

  • CPU密集型程序一般指的是程序主要消耗CPU资源而非I/O资源,例如图像处理、加密解密等计算密集型任务。在这种情况下,epoll模型可能不是最优的选择,因为epoll主要用于处理I/O操作,而非CPU计算。
  • 在CPU密集型程序中,通常采用多线程或者多进程来利用多核CPU进行并行计算。如果程序中存在少量的I/O操作,也可以采用阻塞I/O或者非阻塞I/O的方式进行操作。这样可以减少I/O操作对CPU的干扰,提高CPU的利用率。
  • 需要注意的是,如果CPU密集型程序中存在大量的I/O操作,并且需要处理大量的并发连接,例如Web服务器等,那么使用epoll模型可以大大提高程序的I/O效率和性能,从而提高程序的吞吐量和并发性能。但是,如果I/O操作比例较小,就没有必要使用epoll模型了。
Edit this page
最后更新时间:
贡献者: hyfly233