Linux——高级IO

一、前言概念

IO=拷贝+等待

1. 同步(Synchronous) vs 异步(Asynchronous)

  • 核心区别:关注的是消息通知的机制

    • 同步:调用方主动等待结果,需持续检查任务是否完成。

    • 异步:调用方发起任务后无需等待,被调用方完成后主动通知(如回调函数、信号)。

生活例子
  • 同步
    你去餐厅点餐后,坐在座位上一直盯着取餐屏,直到显示你的订单号,然后去取餐。

  • 异步
    你通过外卖APP下单,之后去做其他事,外卖小哥送到后打电话通知你


2. 阻塞(Blocking) vs 非阻塞(Non-Blocking)

  • 核心区别:关注的是等待时的线程状态

    • 阻塞:调用方在等待结果时线程被挂起,无法执行其他操作。

    • 非阻塞:调用方在等待结果时线程可继续执行其他任务,需通过轮询或事件驱动获取结果。

生活例子
  • 阻塞
    你在快递柜前排队取快递,队伍不动时你干站着等待,无法做其他事。

  • 非阻塞
    你在快递柜扫码后,发现快递未到,系统让你“稍后再试”,于是你先去买菜,过会儿再来扫码查看。


组合场景:同步/异步 + 阻塞/非阻塞

例子对比
组合类型生活场景编程类比
同步阻塞排队等咖啡,队伍不动时你一直盯着柜台,不能玩手机。read() 调用后线程挂起等待。
同步非阻塞等水烧开时,你每隔1分钟去看一眼,其他时间可以看书。轮询检查文件是否可读。
异步非阻塞你启动扫地机器人打扫房间,它完成后自动发消息通知你,期间你正常办公。异步I/O + 回调函数。

关键总结

  1. 同步 vs 异步

    • 同步需要主动关注结果,异步由对方通知结果

    • 同步的例子:刷微博等待页面加载;异步的例子:下载大文件时后台运行,完成后弹窗提醒。

  2. 阻塞 vs 非阻塞

    • 阻塞会卡住当前流程,非阻塞允许并行处理其他任务

    • 阻塞的例子:ATM机转账时界面卡住;非阻塞的例子:微信发消息后界面仍可操作。

  3. 常见误区

    • 非阻塞 ≠ 异步:非阻塞可能仍需轮询(同步),而异步一定依赖通知机制。

    • 同步可以是非阻塞:比如边轮询边做其他事(如等洗衣机时拖地)。


技术场景举例

  • 同步阻塞:传统HTTP请求(等待服务器响应时页面卡住)。

  • 同步非阻塞:游戏循环中轮询键盘输入,同时更新画面。

  • 异步非阻塞:Node.js通过回调函数处理高并发网络请求。


二、五种I/O

1. 阻塞I/O(Blocking I/O)

  • 原理:程序发起I/O操作后,线程被挂起,直到数据完全准备好并拷贝到用户空间。

  • 特点:全程等待,无法执行其他任务。

  • 生活例子
    你去餐厅点餐后,站在柜台前一直等待,直到厨师做好并递给你,期间不能做其他事。


2. 非阻塞I/O(Non-Blocking I/O)

  • 原理:程序发起I/O操作后立即返回状态(未就绪则报错),需轮询检查数据是否就绪,就绪后仍需等待数据拷贝。

  • 特点:轮询消耗资源,但等待期间可处理其他任务。

  • 生活例子
    点餐后你回到座位,每隔5分钟去问“好了吗?”,期间可以玩手机。但餐好后仍需在柜台等待打包(拷贝数据阶段阻塞)。


3. I/O多路复用(I/O Multiplexing)

  • 原理:通过select/epoll等机制,单线程监控多个I/O事件,任一就绪时通知程序处理。

  • 特点:高效管理多个连接,适合高并发场景。

  • 生活例子
    餐厅安排一个服务员监听多桌顾客的需求,当某桌的餐准备好时,服务员主动通知该桌取餐。


4. 信号驱动I/O(Signal-Driven I/O)

  • 原理:数据准备阶段内核发送信号(如SIGIO)通知程序,但数据拷贝阶段仍需程序主动处理(可能阻塞)。

  • 特点:避免轮询,但信号处理复杂且可能延迟。

  • 生活例子
    点餐后餐厅给你一个叫号器,餐准备好时震动提醒,但取餐时仍需等待店员打包(拷贝阶段阻塞)。


5. 异步I/O(Asynchronous I/O)

  • 原理:程序发起I/O操作后立即返回,内核完成**全部操作(准备+拷贝)**后通知程序。

  • 特点:全程无阻塞,效率最高。

  • 生活例子
    你通过外卖APP下单,之后继续工作。外卖小哥从制作到送货全程处理,送到后敲门通知你。


总结对比

模型等待阶段数据拷贝阶段生活场景类比
阻塞I/O全程阻塞阻塞柜台前干等餐
非阻塞I/O轮询检查阻塞反复询问餐是否做好
I/O多路复用单线程监控多路阻塞服务员监听多桌需求
信号驱动I/O信号通知阻塞叫号器提醒后取餐
异步I/O完全不阻塞内核完成外卖全程无需等待,送货上门

通过以上例子,可以直观理解不同I/O模型在资源利用和响应方式上的差异。

三、阻塞及非阻塞I/O函数

1. 阻塞I/O的readwrite

功能
  • read:从文件描述符(如文件、套接字、管道等)读取数据,若数据未就绪,线程会被挂起,直到数据到达或发生错误。

  • write:向文件描述符写入数据,若缓冲区已满,线程会被挂起,直到缓冲区可用或发生错误。

特点
  • 同步阻塞:调用后线程无法执行其他任务,直到操作完成。

  • 简单易用:适合简单场景,但高并发时性能差。

代码示例
// 阻塞读取数据
char buffer[1024];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read == -1) {perror("read error");
}// 阻塞写入数据
ssize_t bytes_written = write(fd, buffer, bytes_read);
if (bytes_written == -1) {perror("write error");
}
生活例子
  • 你打电话给朋友,对方未接听时,你一直举着手机等待,直到对方接听或挂断(类似read阻塞等待数据)。


2. 非阻塞I/O的fcntl函数

功能
  • fcntl(File Control):用于修改文件描述符的属性,例如设置非阻塞模式。

  • 关键操作:通过F_SETFL命令设置O_NONBLOCK标志,使后续的read/write调用变为非阻塞。

  • fcntl函数有5种功能:

    复制一个现有的描述符(cmd=F_DUPFD) .

    获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

    获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

特点
  • 控制文件描述符状态:不直接执行I/O操作,而是修改I/O行为。

  • 需配合循环检查:非阻塞模式下,需处理EAGAINEWOULDBLOCK错误(表示数据未就绪)。

代码示例
#include <fcntl.h>// 将文件描述符设置为非阻塞模式
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);// 非阻塞读取数据
char buffer[1024];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 数据未就绪,稍后重试} else {perror("read error");}
}// 非阻塞写入数据(需处理部分写入情况)
ssize_t bytes_written = write(fd, buffer, bytes_read);
if (bytes_written == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 缓冲区已满,稍后重试} else {perror("write error");}
}
生活例子
  • 你给朋友发短信后,每隔几分钟检查手机是否有回复,期间可以处理其他事情(类似非阻塞read轮询检查数据)。

  • 实现函数SetNoBlock
     

    void SetNoBlock(int fd) {
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0) {
    perror("fcntl");
    return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
    }

    轮询方式读取标准输入
     

    void SetNoBlock(int fd)
    {int fl = fcntl(fd, F_GETFL);if (fl < 0){perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
    }
    int main()
    {SetNoBlock(0);while (1){char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0){perror("read");sleep(1);continue;}printf("input:%s\n", buf);}return 0;
    }
    

阻塞 vs 非阻塞I/O的核心区别

特性阻塞I/O非阻塞I/O
线程状态调用后线程挂起,直到操作完成调用后立即返回,线程可执行其他任务
错误处理通常直接返回错误需检查EAGAINEWOULDBLOCK
适用场景简单任务、低并发高并发、实时响应需求
代码复杂度简单需处理轮询或事件驱动机制

关键注意事项

  1. 非阻塞I/O的局限性

    • 非阻塞read/write可能只处理部分数据(如TCP套接字),需循环调用直到完成。

    • 需结合select/poll/epoll等I/O多路复用技术,避免忙等待(CPU空转)。

  2. fcntl的常见用途

    • 设置非阻塞模式(O_NONBLOCK)。

    • 获取/设置文件描述符状态(如F_GETFD/F_SETFD)。


总结

  • 阻塞I/O:通过read/write实现简单同步操作,但线程效率低。

  • 非阻塞I/O:通过fcntl设置O_NONBLOCK标志,使read/write立即返回,需结合轮询或事件驱动机制。

  • 实际应用:非阻塞I/O常用于高性能服务器(如Nginx、Redis)或需要实时响应的场景。

四、I/O多路转接select

1. select 的作用

select 是一种 同步I/O多路复用 机制,允许程序同时监听多个文件描述符(如套接字、管道等),并在一或多个描述符就绪(可读、可写、异常)时通知程序处理。

  • 核心目标:避免为每个I/O操作创建独立线程,用单线程高效管理多个I/O任务。

  • 适用场景:网络服务器、需要同时处理多客户端请求的场景。


2. select 函数原型

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数解析
参数说明
nfds监听的文件描述符最大值 +1(例如最大描述符为5,则 nfds=6)。
readfds监听可读事件的文件描述符集合。
writefds监听可写事件的文件描述符集合。
exceptfds监听异常事件的文件描述符集合(如带外数据)。
timeout超时时间(NULL表示阻塞等待,0表示非阻塞立即返回,其他为具体时间)。
返回值
  • 成功:返回就绪的文件描述符总数。

  • 超时:返回 0

  • 错误:返回 -1,并设置 errno


3. select 的工作流程

  1. 初始化描述符集合
    使用 FD_ZEROFD_SET 等宏操作 fd_set 结构,设置需要监听的文件描述符。

  2. fd_set 是 select 实现多路复用的核心数据结构,本质是 位数组

// 通常定义在 <sys/select.h>
#define FD_SETSIZE 1024   // 最大支持的文件描述符数量typedef struct {unsigned long fds_bits[FD_SETSIZE / (8 * sizeof(unsigned long))];
} fd_set;
  1. 调用 select
    阻塞或非阻塞等待监听的文件描述符就绪。

  2. 检查就绪状态
    通过 FD_ISSET 宏遍历所有描述符,确定哪些描述符已就绪。

  3. 处理就绪的I/O
    对就绪的描述符执行读/写操作,处理完成后重置监听集合。


4. 关键宏函数

// 清空集合
FD_ZERO(fd_set *set);// 将描述符加入集合
FD_SET(int fd, fd_set *set);// 将描述符移出集合
FD_CLR(int fd, fd_set *set);// 检查描述符是否在集合中
FD_ISSET(int fd, fd_set *set);

5. 代码示例(TCP服务器监听客户端连接和数据)

#include <sys/select.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {int server_fd = socket(AF_INET, SOCK_STREAM, 0);// 绑定、监听等操作(省略)...fd_set readfds;int max_fd = server_fd;while (1) {FD_ZERO(&readfds);FD_SET(server_fd, &readfds); // 监听服务器套接字// 设置超时时间为5秒struct timeval timeout = {5, 0};// 调用select监听可读事件int ret = select(max_fd + 1, &readfds, NULL, NULL, &timeout);if (ret == -1) {perror("select error");exit(1);} else if (ret == 0) {printf("Timeout, no data.\n");continue;}// 检查服务器套接字是否有新连接if (FD_ISSET(server_fd, &readfds)) {int client_fd = accept(server_fd, NULL, NULL);FD_SET(client_fd, &readfds);max_fd = (client_fd > max_fd) ? client_fd : max_fd;}// 遍历其他客户端套接字检查数据for (int fd = server_fd + 1; fd <= max_fd; fd++) {if (FD_ISSET(fd, &readfds)) {char buffer[1024];ssize_t bytes = read(fd, buffer, sizeof(buffer));if (bytes > 0) {// 处理数据...} else {close(fd);FD_CLR(fd, &readfds);}}}}return 0;
}

6. select 的优缺点

优点
  • 跨平台:支持所有主流操作系统(Linux、Windows、macOS等)。

  • 简单易用:适合少量并发连接的管理。

缺点
  • 性能瓶颈

    • 文件描述符数量限制(通常为 FD_SETSIZE=1024)。

    • 每次调用需遍历所有描述符,时间复杂度为 O(n)

  • 重复初始化:每次调用需重新设置监听集合。

  • 内核-用户态拷贝:描述符集合需在用户态和内核态之间拷贝。


7. 生活例子

想象一个 客服中心 的场景:

  • select 的作用:一个客服人员(单线程)同时监听多个来电(文件描述符)。

  • 流程

    1. 将所有来电号码加入监听列表(FD_SET)。

    2. 每隔一段时间检查哪些电话已接通(select 返回就绪描述符)。

    3. 处理接通的电话(读/写数据),挂断后移除监听列表(FD_CLR)。

    4. 若有新来电,加入监听列表(FD_SET)。


8. select 的替代方案

  • poll:改进文件描述符数量限制,但仍有性能问题。

  • epoll(Linux):高效的事件驱动模型,支持海量并发连接。

  • kqueue(BSD/macOS):类似 epoll 的高性能机制。


总结

  • select 适用场景:小型服务器、跨平台程序或对并发要求不高的场景。

  • 核心思想:单线程通过轮询管理多个I/O任务,避免多线程资源竞争。

  • 学习意义:理解多路复用的基础原理,为掌握更高效的 epoll/kqueue 打下基础。

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

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

相关文章

Linux:基础IO

文章目录 一、理解"文件"1、狭义上的理解2、广义上的理解3、文件操作的认知4、系统角度 二、C语言文件接口1、ls /proc/[进程id] -l 命令查看当前正在运⾏进程的信息2、stdin 和 stdout 和 stderr 三、系统文件 I/O1、标志位传递的一种方法2、系统调用 open 三、文件…

zabbix数据采集以及自定义监控

目录 1.数据采集 2.自定义监控 2.1客户端 2.2服务端 ​​​​​​​1.数据采集 点击最新数据页面如下图 往下滑查看具体数据 并点击查看图像就可以看到图像了 就可以看到如下图内容 2.自定义监控 我们通过zabbix客户端任何模板就可以监控我们想要的任何资源 如果…

02.25 继承和多态

编写一个如下场景&#xff1a; 有一个英雄Hero类&#xff0c;私有成员&#xff0c;攻击&#xff0c;防御&#xff0c;速度&#xff0c;生命值&#xff0c;以及所有的set get 方法 编写一个 武器 Weapon 类&#xff0c;拥有私有成员攻击力&#xff0c;以及set get 方法 编写一个…

6. grafana的graph简介

1. Settings功能 2. Visualization功能 &#xff08;可视化的方式&#xff0c;后续会写一些&#xff09; 3. Display 功能&#xff08;显示方面的设置&#xff09; bars 柱状图方式显示 lines&#xff08;不选不会出功能&#xff09; line width 线条的粗细 staircase 会让折…

前缀和代码解析

前缀和是指数组一定范围的数的总和,常见的有两种,一维和二维,我会用两道题来分别解析 一维 DP34 【模板】前缀和 题目: 题目解析: 暴力解法 直接遍历数组,遍历到下标为 l 时,开始进行相加,直到遍历到下标为 r ,最后返回总和.这样做的时间复杂度为: O(n) public class Main …

RoCBert:具有多模态对比预训练的健壮中文BERT

摘要 大规模预训练语言模型在自然语言处理&#xff08;NLP&#xff09;任务上取得了最新的最优结果&#xff08;SOTA&#xff09;。然而&#xff0c;这些模型容易受到对抗攻击的影响&#xff0c;尤其是对于表意文字语言&#xff08;如中文&#xff09;。 在本研究中&#xff0…

【原创工具】文件清单生成器 By怜渠客

【原创工具】文件清单生成器 By怜渠客 刚在论坛看到了一个文件列表生成器 文件列表生成器 - 吾爱破解 - 52pojie.cn &#xff0c;和我去年写的一个软件很像&#xff0c;当时我也是有需求&#xff0c;要把一个文件夹里及其子文件夹里所有的文件列出来&#xff0c;就临时弄了个小…

深度学习-6.用于计算机视觉的深度学习

Deep Learning - Lecture 6 Deep Learning for Computer Vision 简介深度学习在计算机视觉领域的发展时间线 语义分割语义分割系统的类型上采样层语义分割的 SegNet 架构软件中的SegNet 架构数据标注 目标检测与识别目标检测与识别问题两阶段和一阶段目标检测与识别两阶段检测器…

【Linux】初始操作系统和进程(一)

目录 前言&#xff1a; 一、冯诺依曼体系结构&#xff1a; 二、操作系统&#xff1a; 1.操作系统是什么&#xff1f; 2.为什么要有操作系统&#xff1f; 3.操作系统是如何管理下层的&#xff1f; 4.操作系统是如何对上层提供服务的&#xff1f; 三、进程&#xff1a; …

【链 表】

【链表】 一级目录1. 基本概念2. 算法分析2.1 时间复杂度2.2 空间复杂度2.3 时空复杂度互换 线性表的概念线性表的举例顺序表的基本概念顺序表的基本操作1. 初始化2. 插入操作3. 删除操作4. 查找操作5. 遍历操作 顺序表的优缺点总结优点缺点 树形结构图形结构单链表基本概念链表…

python-leetcode-字符串解码

394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; class Solution:def decodeString(self, s: str) -> str:stack []num 0curr_str ""for char in s:if char.isdigit():num num * 10 int(char)elif char [:stack.append((curr_str, num))curr_str, …

力扣 下一个排列

交换位置&#xff0c;双指针&#xff0c;排序。 题目 下一个排列即在组成的排列中的下一个大的数&#xff0c;然后当这个排列为降序时即这个排列最大&#xff0c;因为大的数在前面&#xff0c;降序排列的下一个数即升序。所以&#xff0c;要是想找到当前排列的下一个排列&…

ProGuard加密混淆SpringBoot应用代码

背景 我们的项目是基于SpringCloud架构的微服务应用&#xff0c;采用Docker离线部署方式交付客户&#xff0c;通过授权证书来控制应用的许可功能模块和使用时间。我们已经在代码层已经实现&#xff1a; 基于多维度硬件指纹的绑定验证&#xff0c;cpu id、mac地址、磁盘序列、…

动态链接器(九):.init和.init_array

ELF文件中的.init和.init_array段是程序初始化阶段的重要组成部分&#xff0c;用于在main函数执行前完成必要的初始化操作。 1 .init段和.init_array 段 1.1 作用 .init段包含编译器生成的初始化代码&#xff0c;通常由运行时环境&#xff08;如C标准库的启动例程&#xff0…

Ollama微调

Ollama是一款开源工具&#xff0c;其目标是简化大语言模型在本地环境的部署和使用。它支持多种流行的开源大语言模型&#xff0c;如 Llama 2、Qwen2.5等。在上一篇文章中我们部署Ollama&#xff0c;并使用简单命令管理Ollama。接下来我们学习Ollama的高级应用。通过Ollama的Mod…

DeepSeek开源周Day1:FlashMLA引爆AI推理性能革命!

项目地址&#xff1a;GitHub - deepseek-ai/FlashMLA 开源日历&#xff1a;2025-02-24起 每日9AM(北京时间)更新&#xff0c;持续五天&#xff01; ​ 一、开源周震撼启幕 继上周预告后&#xff0c;DeepSeek于北京时间今晨9点准时开源「FlashMLA」&#xff0c;打响开源周五连…

(七)懒加载预加载

&#xff08;一&#xff09;懒加载 1. 什么是懒加载 懒加载&#xff0c;即延迟加载。在访问页面时&#xff0c;先将 img 元素或其他元素的背景图片路径替换为占位图&#xff08;通常是 1*1px 的小图片&#xff09;&#xff0c;仅当元素进入浏览器可视区域时&#xff0c;才设置…

Revisiting Reverse Distillation for Anomaly Detection

重新审视反向蒸馏在异常检测中的应用 文章链接&#xff1a;点这里 源码链接&#xff1a;点这里 前言 此篇文章是在 Anomaly detection via reverse distillation from one-class embedding 这篇的基础上改进创新的。重新审视了反向蒸馏&#xff08;KD&#xff09;这一想法&am…

Windows CMD 命令大全(Complete List of Windows CMD Commands)

Windows CMD 命令大全&#xff1a; Windows CMD 是 Windows 系统内置的命令行工具&#xff0c;用于执行各种命令和管理任务。 称为Command Prompt。它提供了一个通过键入命令来与计算机系统进行交互的方式&#xff0c;类似于早期的DOS操作系统。以下是 CMD 的基础知识和常用命…

hot100-二叉树

二叉树 二叉树递归 相当于这个的顺序来回调换 class Solution {private List<Integer> res new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {if(root null)return res;inorderTraversal(root.left);res.add(root.val);inorde…