推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

终于明白了,一文彻底理解I/O多路复用

   2023-08-10 网络整理佚名1890
核心提示:从前几节我们知道,所有I/O操作都可以通过文件样的概念来进行,这当然包括网络通信。也就是说通过这个描述符我们就可以和客户端进行通信了。现在你应该明白了吧,处理多个文件描述符的更好方法其实就存在于推销电话中。所谓I/O多路复用指的是这样一个过程:也就是说通过I/O多路复用我们可以同时处理多路I/O。因此我们可以看到,机制的这些特性在高并发网络服务器动辄几万几十万并发链接的场景下无疑是低效的。

本文是高性能高并发系列的第三篇文章。 紧接着上面的“读取文件时,程序经历了什么?” 》,解释完进程、线程、I/O,我们来到高并发中的另一个关键技术,那就是I/O复用。

在解释这项技术之前,我们需要先预览一下文件和文件描述符。

什么是文件

程序员在使用I/O时无法逃避文件的概念。

在Linux世界中,文件是一个非常简单的概念。 作为程序员,我们只需要把它理解为一个N字节的序列:

b1, b2, b3, b4, … bN

事实上,所有的I/O设备都被抽象成了文件的概念,一切都是文件,都是文件,磁盘、网络数据、终端、甚至进程间通信工具管道都被当作文件来对待。

所有的I/O操作也可以通过读写文件来实现。 这种非常优雅的抽象允许程序员使用一组接口在所有外设上执行 I/O 操作。

常用的I/O操作接口一般有以下几类:

程序员可以通过这些接口实现几乎所有的I/O操作,这就是文件概念的力量。

文件描述符

在之前的文章《读文件时,程序经历了什么?》中我们提到,为了进行I/O读操作,比如磁盘数据,我们需要指定一个buff来加载数据,这个buff一般是这样写的:

read(buff);

但这里我们忽略了一个关键问题,那就是虽然我们指定了往哪里写入数据,但是我们应该从哪里读取数据呢?

从上一节我们知道,我们几乎可以通过文件的概念来实现所有的I/O操作,所以这里缺少的主角就是文件。

那么我们一般如何使用文件呢?

周末去热门餐厅吃饭,应该有一些经验。 一般周末人气高的餐厅都会排队,然后服务员会给你一个排队号码,通过这个号码服务员就可以找到你。 这里的好处是服务员不需要记住你是谁,你叫什么名字,你来自哪里,你的爱好是什么,你是否保护环境,爱护小动物等等。这里的重点是服务员对你一无所知,但他们仍然可以通过电话号码找到你。

同样,如果我们想在Linux世界中使用文件,我们也需要使用数字。 根据“不明白原理”,这个数字被称为文件描述符,file,这在Linux界是有名的,其推理过程与上面的队列号相同。

所以,文件描述只是一个数字,但是通过这个数字我们可以操作一个打开的文件,这一点要记住。

有了文件描述符,进程就可以对文件一无所知,比如文件在磁盘上的什么位置、如何加载到内存以及如何管理等。所有这些信息都留给操作系统来获取关心,进程不需要关心它。 系统只需要给进​​程一个文件描述符即可。

那么我们来改进一下上面的流程:

int fd = open(file_name); // 获取文件描述符
read(fd, buff);

怎么样,是不是很简单。

文件描述符过多怎么办

铺垫了这么多,终于来到高性能高并发的话题了。

从前面的章节我们知道,所有的I/O操作都可以通过类似文件的概念来执行,这当然也包括网络通信。

如果你有一个Web服务器,当三方握手成功后,我们会调用获取一个链接,同时我们也会通过调用这个函数得到一个文件描述符,通过它我们可以处理客户端发送的请求并发送处理结果发回。 也就是说我们可以通过这个描述符与客户端进行通信。

// 通过accept获取客户端的文件描述符
int conn_fd = accept(...);

的处理逻辑通常是读取客户端请求数据,然后执行一些具体逻辑:

if(read(conn_fd, request_buff) > 0) {
    do_something(request_buff);
}

是不是很简单,但是世界毕竟复杂,当然没有那么简单。

下一步就更复杂了。

由于我们的主题是高并发的,不可能只与一个客户端通信,而可能同时与数千个客户端通信。 这时,你需要处理的不再是一个描述符那么简单,而是可能要处理上千个描述符。

为了不让问题出现的时候变得太复杂,我们先来简化一下,假设同时只处理两个客户端请求。

有的同学可能会说,这不容易,这样写还不行:

if(read(socket_fd1, buff) > 0) { // 处理第一个
    do_something();
}
if(read(socket_fd2, buff) > 0) { // 处理第二个
    do_something();

在上一篇文章《读取文件时,程序经历了什么?》中我们讨论过这是一个非常典型的阻塞I/O。 如果此时没有数据可读取,则进程将被阻塞并挂起。 这时,我们将无法处理第二个请求,即使第二个请求的数据已经就位,这意味着在处理某个客户端时,所有其他客户端都必须等待,因为进程被阻塞。 当同时处理数以万计的客户时,这显然是无法容忍的。

如果你聪明的话,你肯定会想到使用多线程,为每个客户端请求开启一个线程,这样如果一个客户端被阻塞,也不会影响处理其他客户端的线程。 注意,既然是高并发,那我们就得创建几万个请求,开几千个线程吗? 创建和销毁大量线程会严重影响系统性能。

那么如何解决这个问题呢?

这里的关键点在于,我们事先并不知道某个文件描述对应的I/O设备是否可读或可写,在外设不可读或不可写的状态下执行I/O只会导致进程阻塞被挂起从跑步。

因此,为了优雅地解决这个问题,我们必须从其他角度来思考这个问题。

不要打电话给我,如果我需要你我会打电话给你

每个人一生中肯定都会接到过销售电话,而且销售电话不止一次。 如果你一天接到十八个销售电话,你的身体就会被掏空。

这种情况的关键是,打电话的人不知道你是否想买东西,他们只能一遍又一遍地问你,所以更好的策略是不要让他们给你打电话,写下他们的电话号码,如果需要,请给他们打电话,这样销售人员就不会一遍又一遍地骚扰您(尽管这在现实生活中是不可能的)。

在这个例子中,你就像内核,启动器就像应用程序,电话号码就像文件描述符,和你打电话就像I/O。

现在您应该明白,销售电话中实际上存在处理多个文件描述符的更好方法。

因此,相比于上一节我们通过I/O接口主动询问内核这些文件描述符对应的外设是否准备好,更好的方法是我们将这些感兴趣的文件描述符扔给内核,并霸道地告诉内核:“我这里有10000个文件描述符,你帮我监控,什么时候有文件描述符可以读写你告诉我,我可以处理。” 而不是弱弱地问内核:“第一个文件描述符可以读写吗?第二个文件描述符可以读写吗?第三个文件描述符可以读写吗?……”

这样,应用程序就从“忙碌”的主动变成了悠闲的被动。 不管怎样,如果文件描述可读可写,内核会通知我。 如果我能偷懒的话,我就不会那么勤奋了。

这是一种更高效的I/O处理机制。 现在我们可以同时处理多个 I/O。 我们先给这个机制起个名字,再一次牺牲一下“不懂原理”,那就叫I/O多重吧,这就是I/O。

I/O复用、I/O

该术语实际上主要用于通信领域。 为了充分利用通信线路,希望在一个通道中传输多个信号。 如果要在一个通道中传输多个信号,则需要将多个信号合并为一个,然后将多个信号合并。 形成一种信号的装置称为。 显然,接收端接收到合并后的信号后需要恢复原始的多通道信号。 这个设备的名字叫,如图:

回到我们的主题。

所谓I/O复用是指这样一个过程:

我们得到了一堆文件描述符(无论是网络相关的,还是磁盘文件相关的等等,任何文件描述符都可以)

通过调用某个函数告诉内核:“先不要返回这个函数,你帮我监控这些描述符,当这堆文件描述符中有I/O读写操作时就返回”。

当被调用的函数返回时,我们就可以知道哪些文件描述符可以执行I/O操作。

也就是说,通过I/O复用,我们可以同时处理多个I/O。 那么哪些函数可以用于I/O复用呢?

Linux 世界中可以使用三种 I/O 多路复用机制:

接下来介绍一下牛逼的I/O复用三剑客。

I/O 复用的三个火枪手

本质上,poll和epoll都是阻塞I/O,也就是我们常说的同步I/O。 原因是,如果调用这些 I/O 复用函数时任何需要监视的文件描述符不可读或可写,进程就会被阻塞并挂起,直到有一个文件描述符可读或可写才继续运行。

1. : 雏鸟

在这种I/O复用机制下,我们需要以函数参数的形式告诉我们想要监控的文件描述集,然后将这些文件描述集复制到内核中。 我们知道数据复制是有性能损失的,所以为了减少这种数据复制带来的性能损失,Linux内核对集合的大小进行了限制,规定用户监控的文件描述集合不能超过1024个同时,返回后我们只能知道一些文件描述符是可以读写的,但不知道是哪一个,所以程序员必须再次遍历,找到哪些文件描述符是可以读写的。

因此,概括起来有以下几个特点:

因此,我们可以看到,该机制的这些特性在高并发Web服务器经常有几万、几十万并发连接的场景下无疑是低效的。

2.民意调查:小成功

民意调查和民意调查非常相似。 poll的优化只是为了解决文件描述符数量不能超过1024的限制。poll和poll都会随着监控的文件描述数量的增加而出现性能下降,因此不适合高并发场景。

3. epoll:世界独一无二

面临的三个问题中,文件描述数量的限制在poll中已经解决了,那么epoll又是通过什么技术巧妙的解决了剩下的两个问题呢? 对于这个问题,可以关注公众号“码农荒岛求生”,回复epoll即可获得答案。

总结

基于一切皆文件的设计理念,I/O也可以以文件的形式实现。 在高并发场景下,需要与多个文件进行交互,这离不开高效的I/O复用技术。 本文将详细解释什么是I/O复用以及如何使用它? 其中,以epoll为代表的I/O复用(事件驱动)技术应用广泛。 其实你会发现,凡是涉及到高并发、高性能的场景,基本上都能看到事件驱动的编程方式。

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON