深入探讨NIO

目录

传统阻塞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.mzph.cn/bicheng/62793.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新手参加2025年CTF大赛——Web题目的基本解题流程

CTF&#xff08;Capture the Flag&#xff09;是网络安全比赛中的一种常见形式&#xff0c;参赛者需要通过破解题目、发现漏洞并获取flag&#xff08;标志&#xff09;来获得分数。 这些问题涉及多个领域&#xff0c;如逆向工程、Web安全、密码学、二进制漏洞、取证分析等。CTF…

1Panel 自建邮局 - Docker Mailserver

本文首发于 Anyeの小站&#xff0c;点击链接 访问体验更佳 前言 首先发一段劝退说辞&#xff1a;我相信点进本文的人自建邮局的目的更多地是为了能用自己的域名邮箱&#xff0c;收发邮件&#xff1f; 仅收不发&#xff0c;推荐使用 https://www.cloudflare.com/zh-cn/develop…

QT-thread2种方式选择的优劣对比

1.第一种方式&#xff1a;使用 QObject 的 moveToThread() QObjectQthread class MessageWriter : public QObject {Q_OBJECT public slots:void writeDataToFile(); };threadMsgExchange new QThread();MessageWriter *writer new MessageWriter();writer->moveToThread…

【Maven】功能和核心概念

1. 什么是Maven 1.1 Maven的概念 Maven 是 Apache 软件基金会组织维护的一款自动化构建工具&#xff0c;专注服务于 Java 平台的项目构建和依赖管理。 1.2 为什么要使用Maven&#xff1f; 在项目开发中&#xff0c;我们需要引用各种 jar 包&#xff0c;引用的 jar 包可能有…

Go运行Grule引擎实现计费规则管理

Go运行Grule引擎实现计费规则管理 github位置: https://github.com/hyperjumptech/grule-rule-engine # 安装grule模块 go get -u github.com/hyperjumptech/grule-rule-engineGrule的示例代码 示例位置: https://github.com/hyperjumptech/grule-rule-engine/tree/master/e…

企业网站面临的爬虫攻击及安全防护策略

在当今数字化时代&#xff0c;企业网站不仅是展示企业形象的窗口&#xff0c;更是进行商业活动的重要平台。然而&#xff0c;企业网站在日常运营中面临着多种类型的爬虫攻击&#xff0c;这些攻击不仅会对网站的正常访问造成影响&#xff0c;还可能窃取敏感数据&#xff0c;给企…

Hive on Spark 的Pre-commit 测试

什么是 Pre-Commit 测试&#xff1f; Pre-Commit 测试是一种提交代码到主分支或共享代码库之前运行的一系列自动化测试&#xff0c;用于捕获代码中的潜在问题自动运行的测试流程。其目的是确保新提交的代码不会引入错误&#xff0c;破坏现有功能或降低代码质量。对于大型项目如…

android shader gl_Position是几个分量

在Android的OpenGL ES中&#xff0c;gl_Position是顶点着色器&#xff08;Vertex Shader&#xff09;的一个内置输出变量&#xff0c;它用于指定顶点在裁剪空间&#xff08;Clip Space&#xff09;中的位置。gl_Position是一个四维向量&#xff08;4-component vector&#xff…

【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(6)

1.问题描述&#xff1a; 推送通知到手机&#xff0c;怎么配置拉起应用指定的页面&#xff1f; 解决方案&#xff1a; 1、如果点击通知栏打开默认Ability的话&#xff0c; actionType可以设置为0&#xff0c; 同时可以在.clickAction.data中&#xff0c;指定待跳转的page页面…

vue3 + vite + antdv 项目中自定义图标

前言&#xff1a; 去iconfont-阿里巴巴矢量图标库 下载自己需要的icon图标&#xff0c;下载格式为svg&#xff1b;项目中在存放静态资源的文件夹下 assets 创建一个存放svg格式的图片的文件夹。 步骤&#xff1a; 1、安装vite-plugin-svg-icons npm i vite-plugin-svg-icons …

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本。 原因是&#xff1a;当前操作系统版本为Windows Server 2016 Standard版本&#xff0c;其自带的Microsoft .NET Framework 版本为4.6太低&#xff0c;不满足要求。 根据报错的提示&#xff0c;点击链接…

学习笔记039——SpringBoot整合Redis

文章目录 1、Redis 基本操作Redis 默认有 16 个数据库&#xff0c;使用的是第 0 个&#xff0c;切换数据库添加数据/修改数据查询数据批量添加批量查询删除数据查询所有的 key清除当前数据库清除所有数据库查看 key 是否存在设置有效期查看有效期 2、Redis 数据类型String追加字…

基于yolov8、yolov5的铝材缺陷检测识别系统(含UI界面、训练好的模型、Python代码、数据集)

摘要&#xff1a;铝材缺陷检测在现代工业生产和质量管理中具有重要意义&#xff0c;不仅能帮助企业实时监控铝材质量&#xff0c;还为智能化生产系统提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的铝材缺陷检测模型&#xff0c;该模型使用了大量包含…

如何在 VPS 上使用 Git 设置自动部署

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 介绍 要了解 Git 的基本知识以及如何安装&#xff0c;请参考介绍教程。 本文将教你如何在部署应用程序时使用 Git。虽然有许多使用 Gi…

Goland或Idea启动报错

Goland或Idea启动不了 报错如图&#xff1a; 原因&#xff1a;破解导致 解决方案 环境变量中有关Goland的全部删除

ceph手动部署

ceph手动部署 一、 节点规划 主机名IP地址角色ceph01.example.com172.18.0.10/24mon、mgr、osd、mds、rgwceph02.example.com172.18.0.20/24mon、mgr、osd、mds、rgwceph03.example.com172.18.0.30/24mon、mgr、osd、mds、rgw 操作系统版本&#xff1a; Rocky Linux release …

C#基础之方法

文章目录 1 方法1.1 定义方法1.2 参数传递1.2.1 按值传递参数1.2.2 按引用传递参数1.2.3 按输出传递参数1.2.4 可变参数 params1.2.5 具名参数1.2.6 可选参数 1.3 匿名方法1.3.1 Lambda 表达式1.3.1.1 定义1.3.1.2 常用类型1.3.1.3 Lambda 表达式与 LINQ1.3.1.4 Lambda 表达式的…

Unity——Toggle的状态监听处理

文章目录 前言一、单个Toggle的事件监听二、多个Toggle的事件监听注意事项 前言 在Unity中&#xff0c;Toggle 是一种用户界面&#xff08;UI&#xff09;元素&#xff0c;通常用于提供一个开关选项&#xff0c;允许用户选择开启或关闭某个特定的功能。Toggle 组件有一个 onVa…

PMP–一、二、三模、冲刺–分类–8.质量管理

文章目录 技巧五、质量管理 一模8.质量管理--质量管理计划--质量管理计划包括项目采用的质量标准&#xff0c;到底有没有满足质量需求&#xff0c;看质量标准即可。6、 [单选] 自项目开始以来&#xff0c;作为项目经理同事的职能经理一直公开反对该项目&#xff0c;在讨论项目里…

LabVIEW实现UDP通信

目录 1、UDP通信原理 2、硬件环境部署 3、云端环境部署 4、UDP通信函数 5、程序架构 6、前面板设计 7、程序框图设计 8、测试验证 本专栏以LabVIEW为开发平台,讲解物联网通信组网原理与开发方法,覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合实际案例,展示如何利…