深入探讨NIO

embedded/2024/12/29 2:47:33/

目录

传统阻塞IO

非阻塞IO

select()

epoll

总结

传统阻塞IO

非阻塞IO

IO多路复用select()

IO多路复用epoll


 

传统阻塞IO

在传统的阻塞IO模型中,当一个线程执行到IO操作(如读取数据)时,如果数据尚未准备好,它会阻塞,直到数据准备就绪。这种模型下,每个IO操作都与一个线程紧密绑定,这意味着如果有很多并发的IO操作,就需要创建大量的线程来处理它们,这可能会导致资源消耗过大。

public Order queryOrder() {// 这里会阻塞,直到订单服务返回订单信息,read()方法才会返回Order order = orderConnection.read(); // 查询订单信息log.info("查询订单信息, 收到返回 {}", order);return order;
}
​
// 当执行orderConnection.read()时,如果订单服务没有及时返回数据,
// 线程会阻塞,直到数据到达。在这期间,操作系统会挂起当前线程,
// 释放CPU去执行其他任务,直到IO操作完成,线程才会被唤醒继续执行。
​
// 优点:
// 1. 对开发人员友好,代码简单直观,易于理解和维护。
// 2. 编程模型简单,不需要复杂的状态管理和回调函数。
​
// 缺点:
// 1. IO操作会阻塞整个线程,导致线程资源不能被充分利用。
// 2. 每个连接都需要一个专门的线程来处理,这在高并发场景下会导致线程数量过多。
// 3. 以Java为例,线程默认的栈大小是1M,如果需要同时处理10万个连接,
//    就需要10万个线程,这将消耗100G的栈内存,对系统资源是一个巨大的负担。
​
// 为了避免创建过多的线程,阻塞IO通常与线程池一起使用,这样可以重用线程,
// 减少线程创建和销毁的开销,同时通过线程池来控制并发线程的数量,避免资源耗尽。

非阻塞IO

非阻塞IO调用(如读取或写入)不会使线程挂起等待数据,而是立即返回。如果数据尚未准备好,IO调用会返回一个错误码,告知操作不能立即完成。这种模式允许单个线程管理多个IO连接,但需要不断地检查每个连接的状态。

public void mainLoop() {// 使用O_NONBLOCK选项打开连接,这样IO操作不会阻塞线程Connection conn1 = open(O_NONBLOCK);Connection conn2 = open(O_NONBLOCK);Connection conn3 = open(O_NONBLOCK);List<Connection> connections = List.of(conn1, conn2, conn3);
​// 无限循环,持续检查每个连接是否有数据可读while (true) {for (Connection conn : connections) {// 由于设置了O_NONBLOCK选项,read()方法不会阻塞Object data = conn.read();// 如果有数据可读,处理数据if (data != null) {System.out.println(data);}}}
}
​
// 优点:
// 1. 解决了IO操作导致整个线程挂起的问题,允许一个线程同时处理多个连接。
// 2. 减少了线程数量,降低了线程创建和上下文切换的开销。
​
// 缺点:
// 1. 不停地轮询每个连接是否有数据可读,这可能导致很多无效的检查和高CPU使用率。
// 2. 由于不知道何时会有数据到达,需要频繁地检查每个连接,这可能导致性能问题。
// 3. 编程模型相对复杂,需要额外的逻辑来处理非阻塞IO的回调和事件。

select()

IO多路复用(select())是一种解决非阻塞IO中高CPU轮询问题的技术。它允许单个线程监控多个文件描述符(连接),并在任何一个文件描述符准备好进行IO操作时得到通知。

select()实现细节:

  1. 调用select()时,系统会为所有监控的文件描述符注册回调函数,这些回调函数被存储在文件描述符的wait_queue中。

  2. select线程会被挂起,直到有文件描述符就绪或超时。

  3. 当文件描述符收到数据时,会触发其wait_queue中的回调函数,并唤醒select线程。

  4. 回调函数会标记哪些文件描述符就绪,并从所有文件描述符的wait_queue中移除回调函数,类似于资源清理。

  5. select线程恢复后,可以处理就绪的文件描述符。

#include <sys/select.h>
​
int main(void) {fd_set rfds; // 用于存储需要监听的读就绪文件描述符集合struct timeval tv; // 超时时间设置
​// 主循环for(;;) {// 清空文件描述符集合,为下一次select调用做准备FD_ZERO(&rfds);FD_SET(0, &rfds); // 添加需要监听的文件描述符
​// 调用select阻塞当前线程,直到有文件描述符就绪或超时int retval = select(n, &rfds, NULL, NULL, &tv);if (retval == -1) {perror("select调用出错");} else if (retval) {printf("有连接就绪\n");// 遍历检查哪些文件描述符就绪for (int j = 0; j <= n; j++) {if(FD_ISSET(j, &rfds)) {// 从就绪的文件描述符读取数据recv(j, ...);}}} else {printf("在超时时间内没有任何连接就绪\n");}}return 0;
}
​
​
​
// 优点:
// 1. 实现了wait-notify机制,相比于不停地轮询,效率更高,减少了CPU的无效使用。
​
// 缺点:
// 1. select()的复杂度为O(n),其中n是要监控的文件描述符数量,因为它需要逐个注册和移除回调函数。
// 2. select()只返回哪些文件描述符就绪,实际的数据读取还需要额外调用recv()等函数。
// 3. select()有文件描述符数量的限制,通常限制为1024或2048,这限制了它可以同时监控的文件描述符数量。

epoll

Epoll与select()不同,它通过三个专门的API实现了对大量连接的高效管理,避免了select()在每次操作时都需要对所有连接进行注册和注销回调函数的开销。Epoll的操作分为三个步骤:

  1. epoll_create():这一步是初始化Epoll,准备其内部所需的数据结构。

  2. epoll_ctl():这个API用于动态地向Epoll注册新的连接或者从Epoll中注销已有的连接。(只关心当前操作的连接,不关心所有连接,实现了全量操作向增量操作的优化

  3. epoll_wait():该API使调用线程挂起,直到有连接准备好进行I/O操作或者超过指定的超时时间。

Epoll的优化之处在于:

  • 它通过这三个API将原本需要全量操作的过程转变为增量操作,减少了不必要的重复工作。

  • 内部使用红黑树这种高效的数据结构,将查找和操作的算法复杂度降低到了O(logN),显著提升了处理大量连接时的性能。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
​
#define MAX_EVENTS 10
​
// 定义事件结构体和事件数组
struct epoll_event ev, events[MAX_EVENTS];
​
// 定义套接字和epoll文件描述符
int listen_sock, conn_sock, nfds, epollfd;
​
// 设置监听套接字的代码(socket(), bind(), listen())省略
​
// 创建epoll实例
epollfd = epoll_create1(0);
if (epollfd == -1) {perror("epoll_create1 failed");exit(EXIT_FAILURE);
}
​
// 将监听套接字添加到epoll监听队列
ev.events = EPOLLIN; // 监听读事件
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {perror("epoll_ctl failed for listen_sock");exit(EXIT_FAILURE);
}
​
// 主事件循环
for (;;) {// 等待事件就绪nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);if (nfds == -1) {perror("epoll_wait failed");exit(EXIT_FAILURE);}
​// 处理所有就绪的事件for (int n = 0; n < nfds; ++n) {if (events[n].data.fd == listen_sock) {// 处理新的连接请求conn_sock = accept(listen_sock, (struct sockaddr *) &addr, &addrlen);if (conn_sock == -1) {perror("accept failed");exit(EXIT_FAILURE);}setnonblocking(conn_sock); // 设置非阻塞模式ev.events = EPOLLIN | EPOLLET; // 监听读事件和边缘触发模式ev.data.fd = conn_sock;// 将新连接添加到epoll监听队列if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {perror("epoll_ctl failed for conn_sock");exit(EXIT_FAILURE);}} else {// 处理其他就绪的读写事件do_use_fd(events[n].data.fd); // 根据业务需求处理}}
}
​
// 辅助函数:设置非阻塞模式
void setnonblocking(int sock) {int flags = fcntl(sock, F_GETFL, 0);if (flags == -1) {perror("fcntl F_GETFL failed");exit(EXIT_FAILURE);}flags |= O_NONBLOCK;if (fcntl(sock, F_SETFL, flags) == -1) {perror("fcntl F_SETFL failed");exit(EXIT_FAILURE);}
}
​
// 辅助函数:处理文件描述符
void do_use_fd(int fd) {// 根据业务需求处理fd// 例如:读取数据、写入数据等
}

总结

传统阻塞IO

  • 优点

    • 对开发人员友好,代码编写简单直观。

  • 缺点

    • 连接和线程紧密耦合,每个连接需要一个线程,限制了单机能处理的最大连接数。

    • 为了避免内存耗尽,通常需要配合线程池使用。

非阻塞IO

  • 优点

    • 通过设置O_NONBLOCK标志位,可以让操作系统不挂起当前线程,实现一个线程同时处理多个连接。

  • 缺点

    • 需要不停地轮询检查,效率低,浪费CPU资源。

IO多路复用select()

  • 优点

    • 实现了wait-notify机制,相比轮询效率更高。

  • 缺点

    • 每次调用select()都需要重新准备参数,修改所有连接句柄的wait_queue,算法复杂度较高,为O(n)。n是要监控的连接数

IO多路复用epoll

  • 优点

    • 通过epoll_create()、epoll_ctl()、epoll_wait()三个API,epoll内部管理相关参数和结构,实现增量操作,效率更高,算法复杂度为O(logN)。

  • 缺点

    • 当单个线程管理的连接数过多时,epoll_wait线程本身可能成为瓶颈,可以通过多epoll_wait线程配合多IO线程的策略来解决。


http://www.ppmy.cn/embedded/143188.html

相关文章

爬虫项目基础知识详解

文章目录 Python爬虫项目基础知识一、爬虫与数据分析1.1 Python中的requests库Requests 库的安装Requests 库的 get() 方法爬取网页的通用代码框架HTTP 协议及 Requests 库方法Requests 库主要方法解析 1.2 python中的json库1.3 xpath学习之python中lxml库html了解html结构html…

llvm源码编译

0x00 获取llvm源码 获取llvm项目源码&#xff1a;git clone https://github.com/llvm/llvm-project.git 但是&#xff0c;该项目较大&#xff0c;且直接从github下载源码可能会超时失败。可利用gitee的镜像项目进行clone&#xff1a;git clone --depth 1 https://gitee.com/m…

Maven 内置绑定到底怎么回事?

Maven是一个很好的项目管理工具. 一方面有着众多脚手架&#xff0c;另一方面在依赖管理方面 帮助使用者做了很多准备工作. 随着Maven的使用和学习的深入&#xff0c;大家会不仅有一些问题。 比较浅显的一个就是&#xff0c; 日常我们的Maven 下载安装好以后&#xff0c;在IDE 里…

WebRover :一个功能强大的 Python 库,用于从 Web 内容生成高质量的数据集,专为训练大型语言模型和 AI 应用程序而设计。

2024-11-30 &#xff0c;由Area-25团队开发的一个专门用于生成高质量网络内容数据集的Python库。该数据集旨在为大型语言模型&#xff08;LLM&#xff09;和人工智能应用的训练提供丰富的数据资源。 数据集地址&#xff1a;WebRover Dataset|自然语言处理数据集|AI模型训练数据…

Scala中的正则表达式01

规则类型具体规则示例说明单字符大多数字符匹配自身正则表达式 abc&#xff0c;文本 abca 匹配 a&#xff0c;b 匹配 b&#xff0c;c 匹配 c方括号 [ ][ ] 定义字符集&#xff0c;匹配其一[abc]&#xff0c;文本 a、b 或 c[abc] 匹配 a、b 或者 c排除字符集 [^ ][^ ] 开头加 ^&…

反转字符串中每个单词的字符顺序,但保持单词之间的相对顺序不变(C++)

需求&#xff1a;用户输入一行字符&#xff08;一个英语句子last week, I went to cinima.&#xff09;&#xff0c;将该行字符按照每个单词逆序输出&#xff08;即输出&#xff1a;tsal keew, I tnew ot aminic.&#xff09;。 要求 1.写一个函数用来实现每个单词的字符顺序…

微信小程序全屏显示地图

微信小程序在界面上显示地图&#xff0c;只需要用map标签 <map longitude"经度度数" latitude"纬度度数"></map>例如北京的经纬度为&#xff1a;116.407004,39.904595 <map class"bgMap" longitude"116.407004" lati…

opencvocr识别手机摄像头拍摄的指定区域文字,文字符合规则就语音报警

安装python&#xff0c;pycharm&#xff0c;自行安装。 Python下安装OpenCv 2.1 打开cmd,先安装opencv-python pip install opencv-python --user -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 再安装opencv-contrib-python pip install opencv-contrib-python --user …