epoll 浅析以及 nio 中的 Selector

转载自 epoll 浅析以及 nio 中的 Selector

首先介绍下epoll的基本原理,网上有很多版本,这里选择一个个人觉得相对清晰的讲解(详情见reference):

首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。

不管是文件,还是套接字,还是管道,我们都可以把他们看作流。

之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据;通过write,我们可以往流写入数据。现在假定一个情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办?

阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。

非阻塞忙轮询:接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”

很明显一般人不会用第二种做法,不仅显很无脑,浪费话费不说,还占用了快递员大量的时间。

大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。

为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用(你知道它很慢的),当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。

假设有一个管道,进程A为管道的写入方,B为管道的读出方。

假设一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了,这个事件姑且称之为“缓冲区非空”。

但是“缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了,B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。

假设后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满”

也许事件Y1已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。

这四个情形涵盖了四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满(注都是说的内核缓冲区,且这四个术语都是我生造的,仅为解释其原理而造)。这四个I/O事件是进行阻塞同步的根本。(如果不能理解“同步”是什么概念,请学习操作系统的锁,信号量,条件变量等任务同步方面的相关知识)。

然后我们来说说阻塞I/O的缺点。但是阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。

于是再来考虑非阻塞忙轮询的I/O方式,我们发现我们可以同时处理多个流了(把一个流从阻塞模式切换到非阻塞模式再此不予讨论):

1
2
3
4
5
6
whiletrue {
    fori in stream[]; {
        ifi has data
            read until unavailable
    }
}

我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。这里要补充一点,阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象(后文介绍的select以及epoll)处理甚至直接忽略。

为了避免CPU空转,可以引进了一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。代码长这样:

1
2
3
4
5
6
7
whiletrue {
    select(streams[])
    fori in streams[] {
        ifi has data
            read until unavailable
    }
}

于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。

但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,没一次无差别轮询时间就越长。再次

说了这么多,终于能好好解释epoll了

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

在讨论epoll的实现细节之前,先把epoll的相关操作列出:

  • epoll_create 创建一个epoll对象,一般epollfd = epoll_create()
  • epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件

比如

  • epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入
  • epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入
  • epoll_wait(epollfd,…)等待直到注册的事件发生

(注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。

一个epoll模式的代码大概的样子是:

1
2
3
4
5
6
whiletrue {
    active_stream[] = epoll_wait(epollfd)
    fori in active_stream[] {
        read or write till
    }
}

限于篇幅,我只说这么多,以揭示原理性的东西,至于epoll的使用细节,请参考man和google,实现细节,请参阅linux kernel source。

上文提到了epoll可以将哪个流发生了什么事件主动通知,而不需要轮询。那么到底是如何实现通知的呢?

这就和操作系统的原理相关,在内核的最底层是中断,类似系统回调的机制。网卡设备对应一个中断号, 当网卡收到网络端的消息的时候会向CPU发起中断请求, 然后CPU处理该请求. 通过驱动程序 进而操作系统得到通知, 系统然后通知epoll, epoll通知用户代码。它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll在被内核初始化时(操作系统启动),同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配好的对象。

所以简单总结下epoll和select的区别:

进程通过将一个或多个fd传递给select或poll系统调用,阻塞在select;这样select/poll可以帮我们侦测许多fd是否就绪;但是select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限。linux还提供了一个epoll系统调用,epoll是基于事件驱动方式,而不是顺序扫描,当有fd就绪时,立即回调函数rollback

这种epoll的方式是不是能联想起Java nio,关于nio请查看[高并发Java 八] NIO和AIO (https://my.oschina.net/hosee/blog/615269)。

不过nio中的Selector的取名总让我以为是类似于select/poll的模型,但是你会发现,当有数据被准备好时,调用完select()后,会返回一个SelectionKey,SelectionKey表示在某个selector上的某个Channel的数据已经被准备好了。那到底底层实现是使用select/poll还是epoll呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
publicstatic SelectorProvider create() {
PrivilegedAction pa = newGetPropertyAction("os.name");
String osname = (String) AccessController.doPrivileged(pa);
    if("SunOS".equals(osname)) {
        returnnew sun.nio.ch.DevPollSelectorProvider();
    }
    // use EPollSelectorProvider for Linux kernels >= 2.6
    if("Linux".equals(osname)) {
        pa = newGetPropertyAction("os.version");
        String osversion = (String) AccessController.doPrivileged(pa);
        String[] vers = osversion.split("\\.",0);
        if(vers.length >= 2) {
            try{
                intmajor = Integer.parseInt(vers[0]);
                intminor = Integer.parseInt(vers[1]);
                if(major > 2|| (major == 2&& minor >= 6)) {
                    returnnew sun.nio.ch.EPollSelectorProvider();
                }
            }catch(NumberFormatException x) {
                // format not recognized
            }
        }
    }
    returnnew sun.nio.ch.PollSelectorProvider();
}

可以看到在Linux下,内核版本大于2.6时使用epoll,小于2.6时使用poll

Reference:

1. https://www.zhihu.com/question/20122137/answer/14049112

2. http://my.oschina.net/ielts0909/blog/89849



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

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

相关文章

转-SpringBoot——使用外置的Tomcat服务器

转自: SpringBoot——使用外置的Tomcat服务器_架构师的小跟班的博客-CSDN博客_springboot使用外置tomcat1 前言2 修改步骤2.1 修改打包方式(jar -> war)2.2 排除 SprignBoot的Web模块中的Tomcat依赖2.2.1 将嵌入的Tomcat依赖方式改成 pro…

leetcode初级算法6.字符串转整数(atoi)

leetcode初级算法6.字符串转整数(atoi) 仅为个人刷题记录,不提供解题思路 题解与收获 我的解法: public int myAtoi(String s) {//避免魔法值先设spaceString space " ";//如果是空或者是一串空字符串就滚回去!if(s null || …

inner join on 加条件和where加条件_SQL学习笔记 - GROUP BY / JOIN / UNION

最近在DataCamp上学习SQL(基于PostgreSQL)的课程,本文主要记录自己易记混的点,以便日后参考学习,不做原理讲解。GROUP BY(分组)一般和聚合函数一起使用,包括COUNT(),AVG(…

Selector 实现原理

转载自 Selector 实现原理概述 Selector是NIO中实现I/O多路复用的关键类。Selector实现了通过一个线程管理多个Channel,从而管理多个网络连接的目的。 Channel代表这一个网络连接通道,我们可以将Channel注册到Selector中以实现Selector对其的管理。一个C…

转: 深入浅出-网络七层模型

转自 深入浅出-网络七层模型 - sunsky303 - 博客园引言 今天回顾一下--网络七层模型&&网络数据包 网络基本概念 OSI模型 OSI 模型(Open System Interconnection model)是一个由国际标准化组织&#https://www.cnblogs.com/sunsky3…

date转timestamp格式_技术分享 | MySQL:timestamp 时区转换导致 CPU %sys 高的问题

作者:高鹏文章末尾有他著作的《深入理解 MySQL 主从原理 32 讲》,深入透彻理解 MySQL 主从,GTID 相关技术知识。本文为学习记录,可能有误请谅解。本文建议PC端观看,效果更佳。这个问题是一个朋友遇到的风云&#xff0c…

2021年最新springcloud配置中心不生效的版本原因

想直接看结论请到最下面,中间是我的纠错细节 实名吐槽一波cloudAlibaba文档。 github上的官方文档明明白白写着: 2.2.X版本适用于Springboot 2.2.X 彳亍! 于是我将原本的2.6.0版本改成了SpringBoot 2.2.4Release,然后启动报错&a…

python爬新闻并保存csv_用python爬取内容怎么存入 csv 文件中

小白一个,爬取豆瓣电影250作为练习,想把爬取的内容用csv存储,想存但是不知道怎么自己原来代码拼接在一起。 ps:非伸手党,查阅了官方文档,也做了csv读写的练习,就是拼不到一起,不知道该怎么改。求…

idea部署springboot项目到外部tomcat

【README】 本文旨在记录idea部署springboot项目到外部tomcat的步骤; 第一次部署会踩很多坑儿,多查google,多重试; 第一次部署,不建议手动录入依赖,因为有可能遗漏;而且网络上资料很多但也很…

生成configDataContextRefres失败:Error creating bean with name ‘configDataContextRefresher‘

被这个问题折磨了很久,本人解决方法如下,奉劝一句,该看的官方文档还是要看,但是千万别傻傻地照做! 首先编写bootstrap.properties,往里写入: 这些基础配置 然后检查自己是否引入了这个依赖&am…

python怎么用for循环找出最大值_用for循环语句写一个在输入的十个数字中求最大和最小值的python程序应该怎么写?...

“在输入的十个数字中求最大和最小值的 python 代码”这个需求,在不同时间来看,解题思路不同,所需要的 python 知识点不同。 作为萌新的我,为此特意整理了 3 种解法,以及相应的知识点笔记。 解法A:不使用列…

(转)mysql查看连接客户端ip和杀死进程

转自: mysql : show processlist 详解 - _小豪豪 - 博客园最近排查一些MySQL的问题,会经常用到 show processlist,所以在这里把这个命令总结一下,做个备忘,以备不时只需。 首先是几条常用的SQL。 1、按客户…

Java NIO学习笔记之图解ByteBuffer

转载自 Java NIO学习笔记之图解ByteBuffer ByteBuffer前前后后看过好几次了,实际使用也用了一些,总觉得条理不够清晰。 《程序员的思维修炼》一本书讲过,主动学习,要比单纯看资料效果来的好,所以干脆写个详细点的文章来…

小小涉及OpenFeign原理:Could not extract response: no suitable HttpMessageConverter found for response type

一、问题解释(想看总结的去最下面) org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class XXX] and content type [XXX;XXX]凡是报这个错误&am…

apache shiro怎么升级_Springboot整合Shiro之授权

第二条为推广文章,阅读一次0.3kuai,收入用于网站服务器及资源索取。Shiro是我们常用的一个权限管理框架,本文的重点是来介绍下在SpringBoot环境下我们怎么来使用Shiro。一、添加相关依赖本案例中我们使用SpringDataJPA和Thymeleaf来配合讲解&…

转: 虚拟IP(VIP)原理

转自: 虚拟IP(VIP)原理_海阔天空sky的博客-CSDN博客_vip 虚拟ip原理高可用性HA(High Availability)指的是通过尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所…

NIO学习–缓冲区

转载自 NIO学习–缓冲区Buffer其实就是是一个容器对象,它包含一些要写入或者刚读出的数据。在NIO中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中,您将数据直接写入或者将数据直接读到Stream对象中。 在NIO库中&#xff…

谷粒商城RabbitMQ设计思想详解:消息队列双重保险设计

前言 上来先放一张设计图,看这篇文章的前提是一定得写过或者了解这段业务,不然会看不懂,我下面将会给出我的理解,尽量让大家明白 设计思想 TransactionalOverridepublic SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {…

java restful接口开发实例_实战:基于Spring Boot快速开发RESTful风格API接口

写在前面的话这篇文章计划是在过年期间完成的,示例代码都写好了,结果亲戚来我家做客,文章没来得及写。已经很久没有更新文章了,小伙伴们,有没有想我啊。言归正传,下面开始,今天的话题。目标写一…

转:elasticsearch nested嵌套查询

转自: 【弄nng - Elasticsearch】DSL入门篇(七)—— Nested类型查询,聚合_司马缸砸缸了-CSDN博客文章目录1. nested query2. nested 对象聚合项目推荐nested类型就是为了解决object类型在对象数组上丢失关联性的问题的&#xff0…