select、poll、epoll的底层原理剖析

引言

select/poll、epoll 这些词汇相信诸位都不陌生,因为在 Redis/Nginx/Netty 等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容,不过在此之前,先上一张图概述 Java-NIO 的整体结构:

观察上述结构,其实 Buffer、Channel 的定义并不算复杂,仅是单纯的三层结构,因此对于源码这块不再去剖析,有兴趣的根据给出的目录结构去调试源码,自然也能摸透其原理实现。

而最关键的是 Selector 选择器,它是整个 NIO 体系中较为复杂的一块内容,同时它也作为 Java-NIO 与内核多路复用模型的“中间者”,但在上述体系中,却出现了之前未曾提及过的 SelectorProvider 系定义,那么它的作用是干嘛的呢?主要目的是用于创建选择器,在Java中创建一般是通过如下方式:

// 创建Selector选择器
Selector selector = Selector.open();

// Selector类 → open()方法
public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}
复制代码

从源码中可明显得知,选择器最终是由 SelectorProvider 去进行实例化,不过值得一提的是: Selector 的实现是基于工厂模式与 SPI 机制构建的。对于不同 OS 而言,其对应的具体实现并不相同,因此在 Windows 系统下,我们只能观测到 WindowsSelectorXXX 这一系列的实现,而在 Linux 系统时,对于的则是 EPollSelectorXXX 这一系列的实现,所以要牢记的是, Java-NIO 在不同操作系统的环境中,提供了不同的实现 ,如下:

  • Windows : select
  • Unix : poll
  • Mac : kqueue
  • Linux : epoll

当然,本次则重点剖析Linux系统下的 select、poll、epoll 的具体实现,对于其他系统而言,原理大致相同。

一、JDK层面的源码入口

简单的对于 Java-NIO 体系有了全面认知后,接下来以 JDK 源码作为入口进行剖析。在Java中,会通过 Selector.select() 方法去监听事件是否被触发,如下:

// 轮询监听选择器上注册的通道是否有事件被触发
while (selector.select() > 0){}

// Selector抽象类 → select()抽象方法
public abstract int select() throws IOException;

// SelectorImpl类 → select()方法
public int select() throws IOException {
    return this.select(0L);
}
// SelectorImpl类 → select()完整方法
public int select(long var1) throws IOException {
    if (var1 < 0L) {
        throw new IllegalArgumentException("Negative timeout");
    } else {
        return this.lockAndDoSelect(var1 == 0L ? -1L : var1);
    }
}
复制代码

当调用 Selector.select() 方法后,最终会调用到 SelectorImpl 类的 select(long var1) 方法,而在该方法中,又会调用 lockAndDoSelect() 方法,如下:

// SelectorImpl类 → lockAndDoSelect()方法
private int lockAndDoSelect(long var1) throws IOException {
    // 先获取锁确保线程安全
    synchronized(this) {
        // 在判断当前选择是否处于开启状态
        if (!this.isOpen()) {
            // 如果已关闭则抛出异常
            throw new ClosedSelectorException();
        } else { // 如若处于开启状态
            // 获取所有注册在当前选择器上的事件
            Set var4 = this.publicKeys;
            int var10000;
            // 再次加锁
            synchronized(this.publicKeys) {
                // 获取所有已就绪的事件
                Set var5 = this.publicSelectedKeys;
                // 再次加锁
                synchronized(this.publicSelectedKeys) {
                    // 真正的调用select逻辑,获取已就绪的事件
                    var10000 = this.doSelect(var1);
                }
            }
            // 返回就绪事件的数量
            return var10000;
        }
    }
}
复制代码

在该方法中,对于其他逻辑不必太过在意,重点可注意:最终会调用 doSelect() 触发真正的逻辑操作,接下来再看看这个方法:

// SelectorImpl类 → doSelect()方法
protected abstract int doSelect(long var1) throws IOException;

// WindowsSelectorImpl类 → doSelect()方法
protected int doSelect(long var1) throws IOException {
    // 先判断一下选择器上是否还有注册的通道
    if (this.channelArray == null) {
        throw new ClosedSelectorException();
    } else { // 如果有的话
        // 先获取一下阻塞等待的超时时长
        this.timeout = var1;
        // 然后将一些取消的事件从选择器上移除
        this.processDeregisterQueue();
        // 再判断一下是否存在线程中断唤醒
        // 这里主要是结合之前的wakeup()方法唤醒阻塞线程的
        if (this.interruptTriggered) {
            this.resetWakeupSocket();
            return 0;
        } else { // 如果没有唤醒阻塞线程的需求出现
            // 先判断一下辅助线程的数量(守护线程),多则减,少则增
            this.adjustThreadsCount();
            // 更新一下finishLock.threadsToFinish为辅助线程数
            this.finishLock.reset();
            // 唤醒所有的辅助线程
            this.startLock.startThreads();
            try {
                // 设置主线程中断的回调函数
                this.begin();

                try {
                    // 最终执行真正的poll逻辑,开始拉取事件
                    this.subSelector.poll();
                } catch (IOException var7) {
                    this.finishLock.setException(var7);
                }
                // 唤醒并等待所有未执行完的辅助线程完成
                if (this.threads.size() > 0) {
                    this.finishLock.waitForHelperThreads();
                }
            } finally {
                this.end();
            }
            // 检测状态
            this.finishLock.checkForException();
            this.processDeregisterQueue();
            // 获取当前选择器监听的事件的触发数量
            int var3 = this.updateSelectedKeys();
            // 本轮poll结束,重置WakeupSocket,为下次执行做准备
            this.resetWakeupSocket();
            // 最终返回获取到的事件数
            return var3;
        }
    }
}
复制代码

整个过程下来其实也并不短暂,但大体就分为三步:

poll

在这里面࿰

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值